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