1 /* 2 * Copyright (C) 2016 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.incall.impl; 18 19 import android.support.annotation.CallSuper; 20 import android.support.annotation.DrawableRes; 21 import android.support.annotation.NonNull; 22 import android.support.annotation.StringRes; 23 import android.telecom.CallAudioState; 24 import android.text.TextUtils; 25 import android.view.View; 26 import android.view.View.OnClickListener; 27 import com.android.dialer.common.Assert; 28 import com.android.incallui.incall.impl.CheckableLabeledButton.OnCheckedChangeListener; 29 import com.android.incallui.incall.protocol.InCallButtonIds; 30 import com.android.incallui.incall.protocol.InCallButtonUiDelegate; 31 import com.android.incallui.incall.protocol.InCallScreenDelegate; 32 33 /** Manages a single button. */ 34 interface ButtonController { 35 isEnabled()36 boolean isEnabled(); 37 setEnabled(boolean isEnabled)38 void setEnabled(boolean isEnabled); 39 isAllowed()40 boolean isAllowed(); 41 setAllowed(boolean isAllowed)42 void setAllowed(boolean isAllowed); 43 setChecked(boolean isChecked)44 void setChecked(boolean isChecked); 45 46 @InCallButtonIds getInCallButtonId()47 int getInCallButtonId(); 48 setButton(CheckableLabeledButton button)49 void setButton(CheckableLabeledButton button); 50 51 final class Controllers { 52 resetButton(CheckableLabeledButton button)53 private static void resetButton(CheckableLabeledButton button) { 54 if (button != null) { 55 button.setOnCheckedChangeListener(null); 56 button.setOnClickListener(null); 57 } 58 } 59 } 60 61 abstract class CheckableButtonController implements ButtonController, OnCheckedChangeListener { 62 63 @NonNull protected final InCallButtonUiDelegate delegate; 64 @InCallButtonIds protected final int buttonId; 65 @StringRes protected final int checkedDescription; 66 @StringRes protected final int uncheckedDescription; 67 protected boolean isEnabled; 68 protected boolean isAllowed; 69 protected boolean isChecked; 70 protected CheckableLabeledButton button; 71 CheckableButtonController( @onNull InCallButtonUiDelegate delegate, @InCallButtonIds int buttonId, @StringRes int checkedContentDescription, @StringRes int uncheckedContentDescription)72 protected CheckableButtonController( 73 @NonNull InCallButtonUiDelegate delegate, 74 @InCallButtonIds int buttonId, 75 @StringRes int checkedContentDescription, 76 @StringRes int uncheckedContentDescription) { 77 Assert.isNotNull(delegate); 78 this.delegate = delegate; 79 this.buttonId = buttonId; 80 this.checkedDescription = checkedContentDescription; 81 this.uncheckedDescription = uncheckedContentDescription; 82 } 83 84 @Override isEnabled()85 public boolean isEnabled() { 86 return isEnabled; 87 } 88 89 @Override setEnabled(boolean isEnabled)90 public void setEnabled(boolean isEnabled) { 91 this.isEnabled = isEnabled; 92 if (button != null) { 93 button.setEnabled(isEnabled); 94 } 95 } 96 97 @Override isAllowed()98 public boolean isAllowed() { 99 return isAllowed; 100 } 101 102 @Override setAllowed(boolean isAllowed)103 public void setAllowed(boolean isAllowed) { 104 this.isAllowed = isAllowed; 105 if (button != null) { 106 button.setVisibility(isAllowed ? View.VISIBLE : View.INVISIBLE); 107 } 108 } 109 110 @Override setChecked(boolean isChecked)111 public void setChecked(boolean isChecked) { 112 this.isChecked = isChecked; 113 if (button != null) { 114 button.setChecked(isChecked); 115 } 116 } 117 118 @Override 119 @InCallButtonIds getInCallButtonId()120 public int getInCallButtonId() { 121 return buttonId; 122 } 123 124 @Override 125 @CallSuper setButton(CheckableLabeledButton button)126 public void setButton(CheckableLabeledButton button) { 127 Controllers.resetButton(this.button); 128 129 this.button = button; 130 if (button != null) { 131 button.setEnabled(isEnabled); 132 button.setVisibility(isAllowed ? View.VISIBLE : View.INVISIBLE); 133 button.setChecked(isChecked); 134 button.setOnClickListener(null); 135 button.setOnCheckedChangeListener(this); 136 button.setContentDescription( 137 button.getContext().getText(isChecked ? checkedDescription : uncheckedDescription)); 138 button.setShouldShowMoreIndicator(false); 139 } 140 } 141 142 @Override onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked)143 public void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked) { 144 button.setContentDescription( 145 button.getContext().getText(isChecked ? checkedDescription : uncheckedDescription)); 146 doCheckedChanged(isChecked); 147 } 148 doCheckedChanged(boolean isChecked)149 protected abstract void doCheckedChanged(boolean isChecked); 150 } 151 152 abstract class SimpleCheckableButtonController extends CheckableButtonController { 153 154 @StringRes private final int label; 155 @DrawableRes private final int icon; 156 SimpleCheckableButtonController( @onNull InCallButtonUiDelegate delegate, @InCallButtonIds int buttonId, @StringRes int checkedContentDescription, @StringRes int uncheckedContentDescription, @StringRes int label, @DrawableRes int icon)157 protected SimpleCheckableButtonController( 158 @NonNull InCallButtonUiDelegate delegate, 159 @InCallButtonIds int buttonId, 160 @StringRes int checkedContentDescription, 161 @StringRes int uncheckedContentDescription, 162 @StringRes int label, 163 @DrawableRes int icon) { 164 super( 165 delegate, 166 buttonId, 167 checkedContentDescription == 0 ? label : checkedContentDescription, 168 uncheckedContentDescription == 0 ? label : uncheckedContentDescription); 169 this.label = label; 170 this.icon = icon; 171 } 172 173 @Override 174 @CallSuper setButton(CheckableLabeledButton button)175 public void setButton(CheckableLabeledButton button) { 176 super.setButton(button); 177 if (button != null) { 178 button.setLabelText(label); 179 button.setIconDrawable(icon); 180 } 181 } 182 } 183 184 abstract class NonCheckableButtonController implements ButtonController, OnClickListener { 185 186 protected final InCallButtonUiDelegate delegate; 187 @InCallButtonIds protected final int buttonId; 188 @StringRes protected final int contentDescription; 189 protected boolean isEnabled; 190 protected boolean isAllowed; 191 protected CheckableLabeledButton button; 192 NonCheckableButtonController( InCallButtonUiDelegate delegate, @InCallButtonIds int buttonId, @StringRes int contentDescription)193 protected NonCheckableButtonController( 194 InCallButtonUiDelegate delegate, 195 @InCallButtonIds int buttonId, 196 @StringRes int contentDescription) { 197 this.delegate = delegate; 198 this.buttonId = buttonId; 199 this.contentDescription = contentDescription; 200 } 201 202 @Override isEnabled()203 public boolean isEnabled() { 204 return isEnabled; 205 } 206 207 @Override setEnabled(boolean isEnabled)208 public void setEnabled(boolean isEnabled) { 209 this.isEnabled = isEnabled; 210 if (button != null) { 211 button.setEnabled(isEnabled); 212 } 213 } 214 215 @Override isAllowed()216 public boolean isAllowed() { 217 return isAllowed; 218 } 219 220 @Override setAllowed(boolean isAllowed)221 public void setAllowed(boolean isAllowed) { 222 this.isAllowed = isAllowed; 223 if (button != null) { 224 button.setVisibility(isAllowed ? View.VISIBLE : View.INVISIBLE); 225 } 226 } 227 228 @Override setChecked(boolean isChecked)229 public void setChecked(boolean isChecked) { 230 Assert.fail(); 231 } 232 233 @Override 234 @InCallButtonIds getInCallButtonId()235 public int getInCallButtonId() { 236 return buttonId; 237 } 238 239 @Override 240 @CallSuper setButton(CheckableLabeledButton button)241 public void setButton(CheckableLabeledButton button) { 242 Controllers.resetButton(this.button); 243 244 this.button = button; 245 if (button != null) { 246 button.setEnabled(isEnabled); 247 button.setVisibility(isAllowed ? View.VISIBLE : View.INVISIBLE); 248 button.setChecked(false); 249 button.setOnCheckedChangeListener(null); 250 button.setOnClickListener(this); 251 button.setContentDescription(button.getContext().getText(contentDescription)); 252 button.setShouldShowMoreIndicator(false); 253 } 254 } 255 } 256 257 abstract class SimpleNonCheckableButtonController extends NonCheckableButtonController { 258 259 @StringRes private final int label; 260 @DrawableRes private final int icon; 261 SimpleNonCheckableButtonController( InCallButtonUiDelegate delegate, @InCallButtonIds int buttonId, @StringRes int contentDescription, @StringRes int label, @DrawableRes int icon)262 protected SimpleNonCheckableButtonController( 263 InCallButtonUiDelegate delegate, 264 @InCallButtonIds int buttonId, 265 @StringRes int contentDescription, 266 @StringRes int label, 267 @DrawableRes int icon) { 268 super(delegate, buttonId, contentDescription == 0 ? label : contentDescription); 269 this.label = label; 270 this.icon = icon; 271 } 272 273 @Override 274 @CallSuper setButton(CheckableLabeledButton button)275 public void setButton(CheckableLabeledButton button) { 276 super.setButton(button); 277 if (button != null) { 278 button.setLabelText(label); 279 button.setIconDrawable(icon); 280 } 281 } 282 } 283 284 class MuteButtonController extends SimpleCheckableButtonController { 285 MuteButtonController(InCallButtonUiDelegate delegate)286 public MuteButtonController(InCallButtonUiDelegate delegate) { 287 super( 288 delegate, 289 InCallButtonIds.BUTTON_MUTE, 290 R.string.incall_content_description_muted, 291 R.string.incall_content_description_unmuted, 292 R.string.incall_label_mute, 293 R.drawable.quantum_ic_mic_off_white_36); 294 } 295 296 @Override doCheckedChanged(boolean isChecked)297 public void doCheckedChanged(boolean isChecked) { 298 delegate.muteClicked(isChecked, true /* clickedByUser */); 299 } 300 } 301 302 class SpeakerButtonController 303 implements ButtonController, OnCheckedChangeListener, OnClickListener { 304 305 @NonNull private final InCallButtonUiDelegate delegate; 306 private boolean isEnabled; 307 private boolean isAllowed; 308 private boolean isChecked; 309 private CheckableLabeledButton button; 310 311 @StringRes private int label = R.string.incall_label_speaker; 312 @DrawableRes private int icon = R.drawable.quantum_ic_volume_up_white_36; 313 private boolean checkable; 314 private CharSequence contentDescription; 315 private CharSequence checkedContentDescription; 316 private CharSequence uncheckedContentDescription; 317 SpeakerButtonController(@onNull InCallButtonUiDelegate delegate)318 public SpeakerButtonController(@NonNull InCallButtonUiDelegate delegate) { 319 this.delegate = delegate; 320 } 321 322 @Override isEnabled()323 public boolean isEnabled() { 324 return isEnabled; 325 } 326 327 @Override setEnabled(boolean isEnabled)328 public void setEnabled(boolean isEnabled) { 329 this.isEnabled = isEnabled; 330 if (button != null) { 331 button.setEnabled(isEnabled && isAllowed); 332 } 333 } 334 335 @Override isAllowed()336 public boolean isAllowed() { 337 return isAllowed; 338 } 339 340 @Override setAllowed(boolean isAllowed)341 public void setAllowed(boolean isAllowed) { 342 this.isAllowed = isAllowed; 343 if (button != null) { 344 button.setEnabled(isEnabled && isAllowed); 345 } 346 } 347 348 @Override setChecked(boolean isChecked)349 public void setChecked(boolean isChecked) { 350 this.isChecked = isChecked; 351 if (button != null) { 352 button.setChecked(isChecked); 353 } 354 } 355 356 @Override getInCallButtonId()357 public int getInCallButtonId() { 358 return InCallButtonIds.BUTTON_AUDIO; 359 } 360 361 @Override setButton(CheckableLabeledButton button)362 public void setButton(CheckableLabeledButton button) { 363 this.button = button; 364 if (button != null) { 365 button.setEnabled(isEnabled && isAllowed); 366 button.setVisibility(View.VISIBLE); 367 button.setChecked(isChecked); 368 button.setOnClickListener(checkable ? null : this); 369 button.setOnCheckedChangeListener(checkable ? this : null); 370 button.setLabelText(label); 371 button.setIconDrawable(icon); 372 button.setContentDescription( 373 isChecked ? checkedContentDescription : uncheckedContentDescription); 374 button.setShouldShowMoreIndicator(!checkable); 375 } 376 } 377 setAudioState(CallAudioState audioState)378 public void setAudioState(CallAudioState audioState) { 379 @StringRes int contentDescriptionResId; 380 if ((audioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) 381 == CallAudioState.ROUTE_BLUETOOTH) { 382 checkable = false; 383 isChecked = false; 384 label = R.string.incall_label_audio; 385 386 if ((audioState.getRoute() & CallAudioState.ROUTE_BLUETOOTH) 387 == CallAudioState.ROUTE_BLUETOOTH) { 388 icon = R.drawable.quantum_ic_bluetooth_audio_white_36; 389 contentDescriptionResId = R.string.incall_content_description_bluetooth; 390 } else if ((audioState.getRoute() & CallAudioState.ROUTE_SPEAKER) 391 == CallAudioState.ROUTE_SPEAKER) { 392 icon = R.drawable.quantum_ic_volume_up_white_36; 393 contentDescriptionResId = R.string.incall_content_description_speaker; 394 } else if ((audioState.getRoute() & CallAudioState.ROUTE_WIRED_HEADSET) 395 == CallAudioState.ROUTE_WIRED_HEADSET) { 396 icon = R.drawable.quantum_ic_headset_white_36; 397 contentDescriptionResId = R.string.incall_content_description_headset; 398 } else { 399 icon = R.drawable.ic_phone_audio_white_36dp; 400 contentDescriptionResId = R.string.incall_content_description_earpiece; 401 } 402 } else { 403 checkable = true; 404 isChecked = audioState.getRoute() == CallAudioState.ROUTE_SPEAKER; 405 label = R.string.incall_label_speaker; 406 icon = R.drawable.quantum_ic_volume_up_white_36; 407 contentDescriptionResId = R.string.incall_content_description_speaker; 408 } 409 410 contentDescription = delegate.getContext().getText(contentDescriptionResId); 411 checkedContentDescription = 412 TextUtils.concat( 413 contentDescription, 414 delegate.getContext().getText(R.string.incall_talkback_speaker_on)); 415 uncheckedContentDescription = 416 TextUtils.concat( 417 contentDescription, 418 delegate.getContext().getText(R.string.incall_talkback_speaker_off)); 419 setButton(button); 420 } 421 422 @Override onClick(View v)423 public void onClick(View v) { 424 delegate.showAudioRouteSelector(); 425 } 426 427 @Override onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked)428 public void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked) { 429 checkableLabeledButton.setContentDescription( 430 isChecked ? checkedContentDescription : uncheckedContentDescription); 431 delegate.toggleSpeakerphone(); 432 } 433 } 434 435 class DialpadButtonController extends SimpleCheckableButtonController { 436 DialpadButtonController(@onNull InCallButtonUiDelegate delegate)437 public DialpadButtonController(@NonNull InCallButtonUiDelegate delegate) { 438 super( 439 delegate, 440 InCallButtonIds.BUTTON_DIALPAD, 441 0, 442 0, 443 R.string.incall_label_dialpad, 444 R.drawable.quantum_ic_dialpad_white_36); 445 } 446 447 @Override doCheckedChanged(boolean isChecked)448 public void doCheckedChanged(boolean isChecked) { 449 delegate.showDialpadClicked(isChecked); 450 } 451 } 452 453 class HoldButtonController extends SimpleCheckableButtonController { 454 HoldButtonController(@onNull InCallButtonUiDelegate delegate)455 public HoldButtonController(@NonNull InCallButtonUiDelegate delegate) { 456 super( 457 delegate, 458 InCallButtonIds.BUTTON_HOLD, 459 R.string.incall_content_description_unhold, 460 R.string.incall_content_description_hold, 461 R.string.incall_label_hold, 462 R.drawable.quantum_ic_pause_white_36); 463 } 464 465 @Override doCheckedChanged(boolean isChecked)466 public void doCheckedChanged(boolean isChecked) { 467 delegate.holdClicked(isChecked); 468 } 469 } 470 471 class AddCallButtonController extends SimpleNonCheckableButtonController { 472 AddCallButtonController(@onNull InCallButtonUiDelegate delegate)473 public AddCallButtonController(@NonNull InCallButtonUiDelegate delegate) { 474 super( 475 delegate, 476 InCallButtonIds.BUTTON_ADD_CALL, 477 0, 478 R.string.incall_label_add_call, 479 R.drawable.ic_addcall_white); 480 Assert.isNotNull(delegate); 481 } 482 483 @Override onClick(View view)484 public void onClick(View view) { 485 delegate.addCallClicked(); 486 } 487 } 488 489 class SwapButtonController extends SimpleNonCheckableButtonController { 490 SwapButtonController(@onNull InCallButtonUiDelegate delegate)491 public SwapButtonController(@NonNull InCallButtonUiDelegate delegate) { 492 super( 493 delegate, 494 InCallButtonIds.BUTTON_SWAP, 495 R.string.incall_content_description_swap_calls, 496 R.string.incall_label_swap, 497 R.drawable.quantum_ic_swap_calls_white_36); 498 Assert.isNotNull(delegate); 499 } 500 501 @Override onClick(View view)502 public void onClick(View view) { 503 delegate.swapClicked(); 504 } 505 } 506 507 class MergeButtonController extends SimpleNonCheckableButtonController { 508 MergeButtonController(@onNull InCallButtonUiDelegate delegate)509 public MergeButtonController(@NonNull InCallButtonUiDelegate delegate) { 510 super( 511 delegate, 512 InCallButtonIds.BUTTON_MERGE, 513 R.string.incall_content_description_merge_calls, 514 R.string.incall_label_merge, 515 R.drawable.quantum_ic_call_merge_white_36); 516 Assert.isNotNull(delegate); 517 } 518 519 @Override onClick(View view)520 public void onClick(View view) { 521 delegate.mergeClicked(); 522 } 523 } 524 525 class UpgradeToVideoButtonController extends SimpleNonCheckableButtonController { 526 UpgradeToVideoButtonController(@onNull InCallButtonUiDelegate delegate)527 public UpgradeToVideoButtonController(@NonNull InCallButtonUiDelegate delegate) { 528 super( 529 delegate, 530 InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, 531 0, 532 R.string.incall_label_videocall, 533 R.drawable.quantum_ic_videocam_white_36); 534 Assert.isNotNull(delegate); 535 } 536 537 @Override onClick(View view)538 public void onClick(View view) { 539 delegate.changeToVideoClicked(); 540 } 541 } 542 543 class ManageConferenceButtonController extends SimpleNonCheckableButtonController { 544 545 private final InCallScreenDelegate inCallScreenDelegate; 546 ManageConferenceButtonController(@onNull InCallScreenDelegate inCallScreenDelegate)547 public ManageConferenceButtonController(@NonNull InCallScreenDelegate inCallScreenDelegate) { 548 super( 549 null, 550 InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE, 551 R.string.a11y_description_incall_label_manage_content, 552 R.string.incall_label_manage, 553 R.drawable.quantum_ic_group_white_36); 554 Assert.isNotNull(inCallScreenDelegate); 555 this.inCallScreenDelegate = inCallScreenDelegate; 556 } 557 558 @Override onClick(View view)559 public void onClick(View view) { 560 inCallScreenDelegate.onManageConferenceClicked(); 561 } 562 } 563 564 class SwitchToSecondaryButtonController extends SimpleNonCheckableButtonController { 565 566 private final InCallScreenDelegate inCallScreenDelegate; 567 SwitchToSecondaryButtonController(InCallScreenDelegate inCallScreenDelegate)568 public SwitchToSecondaryButtonController(InCallScreenDelegate inCallScreenDelegate) { 569 super( 570 null, 571 InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY, 572 R.string.incall_content_description_swap_calls, 573 R.string.incall_label_swap, 574 R.drawable.quantum_ic_swap_calls_white_36); 575 Assert.isNotNull(inCallScreenDelegate); 576 this.inCallScreenDelegate = inCallScreenDelegate; 577 } 578 579 @Override onClick(View view)580 public void onClick(View view) { 581 inCallScreenDelegate.onSecondaryInfoClicked(); 582 } 583 } 584 } 585