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.answer.impl.answermethod;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.animation.ValueAnimator.AnimatorUpdateListener;
26 import android.os.Bundle;
27 import android.support.annotation.FloatRange;
28 import android.support.annotation.NonNull;
29 import android.support.annotation.Nullable;
30 import android.text.TextUtils;
31 import android.view.LayoutInflater;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.view.ViewGroup;
36 import android.widget.TextView;
37 import com.android.dialer.common.Assert;
38 import com.android.dialer.common.LogUtil;
39 import com.android.incallui.answer.impl.answermethod.FlingUpDownTouchHandler.OnProgressChangedListener;
40 import com.android.incallui.util.AccessibilityUtil;
41 
42 /** Answer method that shows two buttons for answer/reject. */
43 public class TwoButtonMethod extends AnswerMethod
44     implements OnClickListener, AnimatorUpdateListener {
45 
46   private static final String STATE_HINT_TEXT = "hintText";
47   private static final String STATE_INCOMING_WILL_DISCONNECT = "incomingWillDisconnect";
48 
49   private View answerButton;
50   private View answerLabel;
51   private View declineButton;
52   private View declineLabel;
53   private TextView hintTextView;
54   private boolean incomingWillDisconnect;
55   private boolean buttonClicked;
56   private CharSequence hintText;
57   @Nullable private FlingUpDownTouchHandler touchHandler;
58 
59   @Override
onCreate(@ullable Bundle bundle)60   public void onCreate(@Nullable Bundle bundle) {
61     super.onCreate(bundle);
62     if (bundle != null) {
63       incomingWillDisconnect = bundle.getBoolean(STATE_INCOMING_WILL_DISCONNECT);
64       hintText = bundle.getCharSequence(STATE_HINT_TEXT);
65     }
66   }
67 
68   @Override
onSaveInstanceState(Bundle bundle)69   public void onSaveInstanceState(Bundle bundle) {
70     super.onSaveInstanceState(bundle);
71     bundle.putBoolean(STATE_INCOMING_WILL_DISCONNECT, incomingWillDisconnect);
72     bundle.putCharSequence(STATE_HINT_TEXT, hintText);
73   }
74 
75   @Nullable
76   @Override
onCreateView( LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle)77   public View onCreateView(
78       LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
79     View view = layoutInflater.inflate(R.layout.two_button_method, viewGroup, false);
80 
81     hintTextView = (TextView) view.findViewById(R.id.two_button_hint_text);
82     updateHintText();
83 
84     answerButton = view.findViewById(R.id.two_button_answer_button);
85     answerLabel = view.findViewById(R.id.two_button_answer_label);
86     declineButton = view.findViewById(R.id.two_button_decline_button);
87     declineLabel = view.findViewById(R.id.two_button_decline_label);
88 
89     boolean showLabels = getResources().getBoolean(R.bool.two_button_show_button_labels);
90     answerLabel.setVisibility(showLabels ? View.VISIBLE : View.GONE);
91     declineLabel.setVisibility(showLabels ? View.VISIBLE : View.GONE);
92 
93     answerButton.setOnClickListener(this);
94     declineButton.setOnClickListener(this);
95 
96     if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
97       /* Falsing already handled by AccessibilityManager */
98       touchHandler =
99           FlingUpDownTouchHandler.attach(
100               view,
101               new OnProgressChangedListener() {
102                 @Override
103                 public void onProgressChanged(@FloatRange(from = -1f, to = 1f) float progress) {}
104 
105                 @Override
106                 public void onTrackingStart() {}
107 
108                 @Override
109                 public void onTrackingStopped() {}
110 
111                 @Override
112                 public void onMoveReset(boolean showHint) {}
113 
114                 @Override
115                 public void onMoveFinish(boolean accept) {
116                   if (accept) {
117                     answerCall();
118                   } else {
119                     rejectCall();
120                   }
121                 }
122 
123                 @Override
124                 public boolean shouldUseFalsing(@NonNull MotionEvent downEvent) {
125                   return false;
126                 }
127               },
128               null /* Falsing already handled by AccessibilityManager */);
129       touchHandler.setFlingEnabled(false);
130     }
131     return view;
132   }
133 
134   @Override
onDestroyView()135   public void onDestroyView() {
136     super.onDestroyView();
137     if (touchHandler != null) {
138       touchHandler.detach();
139       touchHandler = null;
140     }
141   }
142 
143   @Override
setHintText(@ullable CharSequence hintText)144   public void setHintText(@Nullable CharSequence hintText) {
145     this.hintText = hintText;
146     updateHintText();
147   }
148 
149   @Override
setShowIncomingWillDisconnect(boolean incomingWillDisconnect)150   public void setShowIncomingWillDisconnect(boolean incomingWillDisconnect) {
151     this.incomingWillDisconnect = incomingWillDisconnect;
152     updateHintText();
153   }
154 
updateHintText()155   private void updateHintText() {
156     if (hintTextView == null) {
157       return;
158     }
159     hintTextView.setVisibility(getActivity().isInMultiWindowMode() ? View.GONE : View.VISIBLE);
160     if (!TextUtils.isEmpty(hintText) && !buttonClicked) {
161       hintTextView.setText(hintText);
162       hintTextView.animate().alpha(1f).start();
163     } else if (incomingWillDisconnect && !buttonClicked) {
164       hintTextView.setText(R.string.call_incoming_will_disconnect);
165       hintTextView.animate().alpha(1f).start();
166     } else {
167       hintTextView.animate().alpha(0f).start();
168     }
169   }
170 
171   @Override
onClick(View view)172   public void onClick(View view) {
173     if (view == answerButton) {
174       answerCall();
175       LogUtil.v("TwoButtonMethod.onClick", "Call answered");
176     } else if (view == declineButton) {
177       rejectCall();
178       LogUtil.v("TwoButtonMethod.onClick", "two_buttonMethod Call rejected");
179     } else {
180       Assert.fail("Unknown click from view: " + view);
181     }
182     buttonClicked = true;
183   }
184 
answerCall()185   private void answerCall() {
186     ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
187     animator.addUpdateListener(this);
188     animator.addListener(
189         new AnimatorListenerAdapter() {
190           private boolean canceled;
191 
192           @Override
193           public void onAnimationCancel(Animator animation) {
194             canceled = true;
195           }
196 
197           @Override
198           public void onAnimationEnd(Animator animation) {
199             if (!canceled) {
200               getParent().answerFromMethod();
201             }
202           }
203         });
204     AnimatorSet animatorSet = new AnimatorSet();
205     animatorSet.play(animator).with(createViewHideAnimation());
206     animatorSet.start();
207   }
208 
rejectCall()209   private void rejectCall() {
210     ValueAnimator animator = ValueAnimator.ofFloat(0, -1);
211     animator.addUpdateListener(this);
212     animator.addListener(
213         new AnimatorListenerAdapter() {
214           private boolean canceled;
215 
216           @Override
217           public void onAnimationCancel(Animator animation) {
218             canceled = true;
219           }
220 
221           @Override
222           public void onAnimationEnd(Animator animation) {
223             if (!canceled) {
224               getParent().rejectFromMethod();
225             }
226           }
227         });
228     AnimatorSet animatorSet = new AnimatorSet();
229     animatorSet.play(animator).with(createViewHideAnimation());
230     animatorSet.start();
231   }
232 
233   @Override
onAnimationUpdate(ValueAnimator animation)234   public void onAnimationUpdate(ValueAnimator animation) {
235     getParent().onAnswerProgressUpdate(((float) animation.getAnimatedValue()));
236   }
237 
createViewHideAnimation()238   private Animator createViewHideAnimation() {
239     ObjectAnimator answerButtonHide =
240         ObjectAnimator.ofPropertyValuesHolder(
241             answerButton,
242             PropertyValuesHolder.ofFloat(View.SCALE_X, 0f),
243             PropertyValuesHolder.ofFloat(View.SCALE_Y, 0f));
244 
245     ObjectAnimator declineButtonHide =
246         ObjectAnimator.ofPropertyValuesHolder(
247             declineButton,
248             PropertyValuesHolder.ofFloat(View.SCALE_X, 0f),
249             PropertyValuesHolder.ofFloat(View.SCALE_Y, 0f));
250 
251     ObjectAnimator answerLabelHide = ObjectAnimator.ofFloat(answerLabel, View.ALPHA, 0f);
252 
253     ObjectAnimator declineLabelHide = ObjectAnimator.ofFloat(declineLabel, View.ALPHA, 0f);
254 
255     ObjectAnimator hintHide = ObjectAnimator.ofFloat(hintTextView, View.ALPHA, 0f);
256 
257     AnimatorSet hideSet = new AnimatorSet();
258     hideSet
259         .play(answerButtonHide)
260         .with(declineButtonHide)
261         .with(answerLabelHide)
262         .with(declineLabelHide)
263         .with(hintHide);
264     return hideSet;
265   }
266 }
267