1 /*
2  * Copyright (C) 2019 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.car.dialer.ui.dialpad;
18 
19 import android.animation.AnimatorInflater;
20 import android.animation.ValueAnimator;
21 import android.os.Bundle;
22 import android.provider.Settings;
23 import android.text.SpannableString;
24 import android.text.Spanned;
25 import android.text.TextUtils;
26 import android.util.SparseArray;
27 import android.view.KeyEvent;
28 import android.view.View;
29 import android.widget.TextView;
30 
31 import androidx.annotation.CallSuper;
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.car.dialer.R;
36 import com.android.car.dialer.log.L;
37 import com.android.car.dialer.ui.common.DialerBaseFragment;
38 import com.android.car.dialer.ui.view.ScaleSpan;
39 
40 /** Fragment that controls the dialpad. */
41 public abstract class AbstractDialpadFragment extends DialerBaseFragment implements
42         KeypadFragment.KeypadCallback {
43     private static final String TAG = "CD.AbsDialpadFragment";
44     private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY";
45     private static final int PLAY_DTMF_TONE = 1;
46 
47     static final SparseArray<Character> sDialValueMap = new SparseArray<>();
48 
49     static {
sDialValueMap.put(KeyEvent.KEYCODE_1, '1')50         sDialValueMap.put(KeyEvent.KEYCODE_1, '1');
sDialValueMap.put(KeyEvent.KEYCODE_2, '2')51         sDialValueMap.put(KeyEvent.KEYCODE_2, '2');
sDialValueMap.put(KeyEvent.KEYCODE_3, '3')52         sDialValueMap.put(KeyEvent.KEYCODE_3, '3');
sDialValueMap.put(KeyEvent.KEYCODE_4, '4')53         sDialValueMap.put(KeyEvent.KEYCODE_4, '4');
sDialValueMap.put(KeyEvent.KEYCODE_5, '5')54         sDialValueMap.put(KeyEvent.KEYCODE_5, '5');
sDialValueMap.put(KeyEvent.KEYCODE_6, '6')55         sDialValueMap.put(KeyEvent.KEYCODE_6, '6');
sDialValueMap.put(KeyEvent.KEYCODE_7, '7')56         sDialValueMap.put(KeyEvent.KEYCODE_7, '7');
sDialValueMap.put(KeyEvent.KEYCODE_8, '8')57         sDialValueMap.put(KeyEvent.KEYCODE_8, '8');
sDialValueMap.put(KeyEvent.KEYCODE_9, '9')58         sDialValueMap.put(KeyEvent.KEYCODE_9, '9');
sDialValueMap.put(KeyEvent.KEYCODE_0, '0')59         sDialValueMap.put(KeyEvent.KEYCODE_0, '0');
sDialValueMap.put(KeyEvent.KEYCODE_STAR, '*')60         sDialValueMap.put(KeyEvent.KEYCODE_STAR, '*');
sDialValueMap.put(KeyEvent.KEYCODE_POUND, '#')61         sDialValueMap.put(KeyEvent.KEYCODE_POUND, '#');
62     }
63 
64     private boolean mDTMFToneEnabled;
65     private final StringBuffer mNumber = new StringBuffer();
66     private ValueAnimator mInputMotionAnimator;
67     private ScaleSpan mScaleSpan;
68     private TextView mTitleView;
69     private int mCurrentlyPlayingTone = KeyEvent.KEYCODE_UNKNOWN;
70 
71     /** Defines how the dialed number should be presented. */
presentDialedNumber(@onNull StringBuffer number)72     abstract void presentDialedNumber(@NonNull StringBuffer number);
73 
74     /** Plays the tone for the pressed keycode when "play DTMF tone" is enabled in settings. */
playTone(int keycode)75     abstract void playTone(int keycode);
76 
77     /** Stops playing all tones when "play DTMF tone" is enabled in settings. */
stopAllTones()78     abstract void stopAllTones();
79 
80     @Override
onCreate(Bundle savedInstanceState)81     public void onCreate(Bundle savedInstanceState) {
82         super.onCreate(savedInstanceState);
83         if (savedInstanceState != null) {
84             mNumber.append(savedInstanceState.getCharSequence(DIAL_NUMBER_KEY));
85         }
86         L.d(TAG, "onCreate, number: %s", mNumber);
87     }
88 
89     @CallSuper
90     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)91     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
92         super.onViewCreated(view, savedInstanceState);
93         mTitleView = view.findViewById(R.id.title);
94         if (mTitleView != null && getResources().getBoolean(R.bool.config_enable_dial_motion)) {
95             mInputMotionAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getContext(),
96                     R.animator.scale_down);
97             float startTextSize = mTitleView.getTextSize() * getResources().getFloat(
98                     R.integer.config_dial_motion_scale_start);
99             mScaleSpan = new ScaleSpan(startTextSize);
100         }
101     }
102 
103     @Override
onResume()104     public void onResume() {
105         super.onResume();
106         mDTMFToneEnabled = Settings.System.getInt(getContext().getContentResolver(),
107                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == PLAY_DTMF_TONE;
108         L.d(TAG, "DTMF tone enabled = %s", String.valueOf(mDTMFToneEnabled));
109 
110         presentDialedNumber();
111     }
112 
113     @Override
onPause()114     public void onPause() {
115         super.onPause();
116         stopAllTones();
117     }
118 
119     @Override
onSaveInstanceState(Bundle outState)120     public void onSaveInstanceState(Bundle outState) {
121         super.onSaveInstanceState(outState);
122         outState.putCharSequence(DIAL_NUMBER_KEY, mNumber);
123     }
124 
125     @Override
onKeypadKeyDown(@eypadFragment.DialKeyCode int keycode)126     public void onKeypadKeyDown(@KeypadFragment.DialKeyCode int keycode) {
127         String digit = sDialValueMap.get(keycode).toString();
128         appendDialedNumber(digit);
129 
130         if (mDTMFToneEnabled) {
131             mCurrentlyPlayingTone = keycode;
132             playTone(keycode);
133         }
134     }
135 
136     @Override
onKeypadKeyUp(@eypadFragment.DialKeyCode int keycode)137     public void onKeypadKeyUp(@KeypadFragment.DialKeyCode int keycode) {
138         if (mDTMFToneEnabled && keycode == mCurrentlyPlayingTone) {
139             mCurrentlyPlayingTone = KeyEvent.KEYCODE_UNKNOWN;
140             stopAllTones();
141         }
142     }
143 
144     /** Set the dialed number to the given number. Must be called after the fragment is added. */
setDialedNumber(String number)145     public void setDialedNumber(String number) {
146         mNumber.setLength(0);
147         if (!TextUtils.isEmpty(number)) {
148             mNumber.append(number);
149         }
150         presentDialedNumber();
151     }
152 
clearDialedNumber()153     void clearDialedNumber() {
154         mNumber.setLength(0);
155         presentDialedNumber();
156     }
157 
removeLastDigit()158     void removeLastDigit() {
159         if (mNumber.length() != 0) {
160             mNumber.deleteCharAt(mNumber.length() - 1);
161         }
162         presentDialedNumber();
163     }
164 
appendDialedNumber(String number)165     void appendDialedNumber(String number) {
166         mNumber.append(number);
167         presentDialedNumber();
168 
169         if (TextUtils.isEmpty(number)) {
170             return;
171         }
172 
173         if (mInputMotionAnimator != null) {
174             final String currentText = mTitleView.getText().toString();
175             final SpannableString spannableString = new SpannableString(currentText);
176             mInputMotionAnimator.addUpdateListener(valueAnimator -> {
177                 float textSize =
178                         (float) valueAnimator.getAnimatedValue() * mTitleView.getTextSize();
179                 mScaleSpan.setTextSize(textSize);
180                 spannableString.setSpan(mScaleSpan, currentText.length() - number.length(),
181                         currentText.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
182                 mTitleView.setText(spannableString, TextView.BufferType.SPANNABLE);
183             });
184             mInputMotionAnimator.start();
185         }
186     }
187 
presentDialedNumber()188     private void presentDialedNumber() {
189         if (mInputMotionAnimator != null) {
190             mInputMotionAnimator.cancel();
191             mInputMotionAnimator.removeAllUpdateListeners();
192         }
193 
194         presentDialedNumber(mNumber);
195     }
196 
197     @NonNull
getNumber()198     StringBuffer getNumber() {
199         return mNumber;
200     }
201 }
202