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