• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.phone;
18 
19 import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.app.Activity;
24 import android.app.AlertDialog;
25 import android.app.Dialog;
26 import android.app.WallpaperManager;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.database.DataSetObserver;
32 import android.graphics.Color;
33 import android.graphics.Point;
34 import android.media.AudioManager;
35 import android.media.ToneGenerator;
36 import android.net.Uri;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.os.PersistableBundle;
40 import android.provider.Settings;
41 import android.telecom.ParcelableCallAnalytics;
42 import android.telecom.PhoneAccount;
43 import android.telecom.TelecomManager;
44 import android.telephony.CarrierConfigManager;
45 import android.telephony.PhoneNumberUtils;
46 import android.telephony.Rlog;
47 import android.telephony.ServiceState;
48 import android.telephony.SubscriptionManager;
49 import android.telephony.TelephonyManager;
50 import android.text.Editable;
51 import android.text.InputType;
52 import android.text.Spannable;
53 import android.text.SpannableString;
54 import android.text.TextUtils;
55 import android.text.TextWatcher;
56 import android.text.method.DialerKeyListener;
57 import android.text.style.TtsSpan;
58 import android.util.Log;
59 import android.util.TypedValue;
60 import android.view.HapticFeedbackConstants;
61 import android.view.KeyEvent;
62 import android.view.MenuItem;
63 import android.view.MotionEvent;
64 import android.view.View;
65 import android.view.View.AccessibilityDelegate;
66 import android.view.ViewGroup;
67 import android.view.WindowManager;
68 import android.view.accessibility.AccessibilityEvent;
69 import android.widget.TextView;
70 
71 import com.android.internal.colorextraction.ColorExtractor;
72 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
73 import com.android.internal.colorextraction.drawable.ScrimDrawable;
74 import com.android.phone.EmergencyDialerMetricsLogger.DialedFrom;
75 import com.android.phone.EmergencyDialerMetricsLogger.PhoneNumberType;
76 import com.android.phone.EmergencyDialerMetricsLogger.UiModeErrorCode;
77 import com.android.phone.common.dialpad.DialpadKeyButton;
78 import com.android.phone.common.util.ViewUtil;
79 import com.android.phone.common.widget.ResizingTextEditText;
80 
81 import java.util.ArrayList;
82 import java.util.List;
83 import java.util.Locale;
84 
85 /**
86  * EmergencyDialer is a special dialer that is used ONLY for dialing emergency calls.
87  *
88  * It's a simplified version of the regular dialer (i.e. the TwelveKeyDialer
89  * activity from apps/Contacts) that:
90  * 1. Allows ONLY emergency calls to be dialed
91  * 2. Disallows voicemail functionality
92  * 3. Allows this activity to stay in front of the keyguard.
93  *
94  * TODO: Even though this is an ultra-simplified version of the normal
95  * dialer, there's still lots of code duplication between this class and
96  * the TwelveKeyDialer class from apps/Contacts.  Could the common code be
97  * moved into a shared base class that would live in the framework?
98  * Or could we figure out some way to move *this* class into apps/Contacts
99  * also?
100  */
101 public class EmergencyDialer extends Activity implements View.OnClickListener,
102         View.OnLongClickListener, View.OnKeyListener, TextWatcher,
103         DialpadKeyButton.OnPressedListener, ColorExtractor.OnColorsChangedListener,
104         EmergencyShortcutButton.OnConfirmClickListener,
105         EmergencyInfoGroup.OnConfirmClickListener {
106 
107     // Keys used with onSaveInstanceState().
108     private static final String LAST_NUMBER = "lastNumber";
109 
110     // Intent action for this activity.
111     public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
112 
113     /**
114      * Extra included in {@link #ACTION_DIAL} to indicate the entry type that user starts
115      * the emergency dialer.
116      */
117     public static final String EXTRA_ENTRY_TYPE =
118             "com.android.phone.EmergencyDialer.extra.ENTRY_TYPE";
119 
120     // Constants indicating the entry type that user opened emergency dialer.
121     // This info is sent from system UI with EXTRA_ENTRY_TYPE. Please make them being
122     // in sync with those in com.android.systemui.util.EmergencyDialerConstants.
123     public static final int ENTRY_TYPE_UNKNOWN = 0;
124     public static final int ENTRY_TYPE_LOCKSCREEN_BUTTON = 1;
125     public static final int ENTRY_TYPE_POWER_MENU = 2;
126 
127     // List of dialer button IDs.
128     private static final int[] DIALER_KEYS = new int[]{
129             R.id.one, R.id.two, R.id.three,
130             R.id.four, R.id.five, R.id.six,
131             R.id.seven, R.id.eight, R.id.nine,
132             R.id.star, R.id.zero, R.id.pound};
133 
134     // Debug constants.
135     private static final boolean DBG = false;
136     private static final String LOG_TAG = "EmergencyDialer";
137 
138     /** The length of DTMF tones in milliseconds */
139     private static final int TONE_LENGTH_MS = 150;
140 
141     /** The DTMF tone volume relative to other sounds in the stream */
142     private static final int TONE_RELATIVE_VOLUME = 80;
143 
144     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
145     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
146 
147     private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0;
148 
149     /** 90% opacity, different from other gradients **/
150     private static final int BACKGROUND_GRADIENT_ALPHA = 230;
151 
152     /** 85% opacity for black background **/
153     private static final int BLACK_BACKGROUND_GRADIENT_ALPHA = 217;
154 
155     /** Size limit of emergency shortcut buttons container. **/
156     private static final int SHORTCUT_SIZE_LIMIT = 3;
157 
158     ResizingTextEditText mDigits;
159     private View mDialButton;
160     private View mDelete;
161     private View mEmergencyShortcutView;
162     private View mDialpadView;
163 
164     private List<EmergencyShortcutButton> mEmergencyShortcutButtonList;
165     private EccShortcutAdapter mShortcutAdapter;
166     private DataSetObserver mShortcutDataSetObserver = null;
167 
168     private ToneGenerator mToneGenerator;
169     private Object mToneGeneratorLock = new Object();
170 
171     // determines if we want to playback local DTMF tones.
172     private boolean mDTMFToneEnabled;
173 
174     private EmergencyActionGroup mEmergencyActionGroup;
175 
176     private EmergencyInfoGroup mEmergencyInfoGroup;
177 
178     // close activity when screen turns off
179     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
180         @Override
181         public void onReceive(Context context, Intent intent) {
182             if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
183                 finishAndRemoveTask();
184             }
185         }
186     };
187 
188     /**
189      * Customize accessibility methods in View.
190      */
191     private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
192 
193         /**
194          * Stop AccessiblityService from reading the title of a hidden View.
195          *
196          * <p>The crossfade animation will set the visibility of fade out view to {@link View.GONE}
197          * in the animation end. The view with an accessibility pane title would call the
198          * {@link AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED} event, which would trigger the
199          * accessibility service to read the pane title of fade out view instead of pane title of
200          * fade in view. So it need to filter out the event called by vanished pane.
201          */
202         @Override
203         public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
204             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
205                     && host.getVisibility() == View.GONE) {
206                 return;
207             }
208             super.onPopulateAccessibilityEvent(host, event);
209         }
210     };
211 
212     private String mLastNumber; // last number we tried to dial. Used to restore error dialog.
213 
214     // Background gradient
215     private ColorExtractor mColorExtractor;
216     private ScrimDrawable mBackgroundDrawable;
217     private boolean mSupportsDarkText;
218 
219     private boolean mIsWfcEmergencyCallingWarningEnabled;
220     private float mDefaultDigitsTextSize;
221 
222     private int mEntryType;
223     private ShortcutViewUtils.Config mShortcutViewConfig;
224 
225     private EmergencyDialerMetricsLogger mMetricsLogger;
226 
227     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)228     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
229         // Do nothing
230     }
231 
232     @Override
onTextChanged(CharSequence input, int start, int before, int changeCount)233     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
234         maybeChangeHintSize();
235     }
236 
237     @Override
afterTextChanged(Editable input)238     public void afterTextChanged(Editable input) {
239         // Check for special sequences, in particular the "**04" or "**05"
240         // sequences that allow you to enter PIN or PUK-related codes.
241         //
242         // But note we *don't* allow most other special sequences here,
243         // like "secret codes" (*#*#<code>#*#*) or IMEI display ("*#06#"),
244         // since those shouldn't be available if the device is locked.
245         //
246         // So we call SpecialCharSequenceMgr.handleCharsForLockedDevice()
247         // here, not the regular handleChars() method.
248         if (SpecialCharSequenceMgr.handleCharsForLockedDevice(this, input.toString(), this)) {
249             // A special sequence was entered, clear the digits
250             mDigits.getText().clear();
251         }
252 
253         updateDialAndDeleteButtonStateEnabledAttr();
254         updateTtsSpans();
255     }
256 
257     @Override
onCreate(Bundle icicle)258     protected void onCreate(Bundle icicle) {
259         super.onCreate(icicle);
260 
261         mMetricsLogger = new EmergencyDialerMetricsLogger(this);
262 
263         mEntryType = getIntent().getIntExtra(EXTRA_ENTRY_TYPE, ENTRY_TYPE_UNKNOWN);
264         Log.d(LOG_TAG, "Launched from " + entryTypeToString(mEntryType));
265 
266         // Allow this activity to be displayed in front of the keyguard / lockscreen.
267         setShowWhenLocked(true);
268         // Allow turning screen on
269         setTurnScreenOn(true);
270 
271         CarrierConfigManager configMgr = getSystemService(CarrierConfigManager.class);
272         PersistableBundle carrierConfig =
273                 configMgr.getConfigForSubId(SubscriptionManager.getDefaultVoiceSubscriptionId());
274 
275         mShortcutViewConfig = new ShortcutViewUtils.Config(this, carrierConfig, mEntryType);
276         Log.d(LOG_TAG, "Enable emergency dialer shortcut: "
277                 + mShortcutViewConfig.isEnabled());
278 
279         mColorExtractor = new ColorExtractor(this);
280 
281         if (mShortcutViewConfig.isEnabled()) {
282             // Shortcut view doesn't support dark text theme.
283             updateTheme(false);
284         } else {
285             GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
286                     ColorExtractor.TYPE_EXTRA_DARK);
287             updateTheme(lockScreenColors.supportsDarkText());
288         }
289 
290         setContentView(R.layout.emergency_dialer);
291 
292         mDigits = (ResizingTextEditText) findViewById(R.id.digits);
293         mDigits.setKeyListener(DialerKeyListener.getInstance());
294         mDigits.setOnClickListener(this);
295         mDigits.setOnKeyListener(this);
296         mDigits.setLongClickable(false);
297         mDigits.setInputType(InputType.TYPE_NULL);
298         mDefaultDigitsTextSize = mDigits.getScaledTextSize();
299         maybeAddNumberFormatting();
300 
301         mBackgroundDrawable = new ScrimDrawable();
302         Point displaySize = new Point();
303         ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
304                 .getDefaultDisplay().getSize(displaySize);
305         mBackgroundDrawable.setAlpha(mShortcutViewConfig.isEnabled()
306                 ? BLACK_BACKGROUND_GRADIENT_ALPHA : BACKGROUND_GRADIENT_ALPHA);
307         getWindow().setBackgroundDrawable(mBackgroundDrawable);
308 
309         // Check for the presence of the keypad
310         View view = findViewById(R.id.one);
311         if (view != null) {
312             setupKeypad();
313         }
314 
315         mDelete = findViewById(R.id.deleteButton);
316         mDelete.setOnClickListener(this);
317         mDelete.setOnLongClickListener(this);
318 
319         mDialButton = findViewById(R.id.floating_action_button);
320 
321         // Check whether we should show the onscreen "Dial" button and co.
322         // Read carrier config through the public API because PhoneGlobals is not available when we
323         // run as a secondary user.
324         if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL)) {
325             mDialButton.setOnClickListener(this);
326         } else {
327             mDialButton.setVisibility(View.GONE);
328         }
329         mIsWfcEmergencyCallingWarningEnabled = carrierConfig.getInt(
330                 CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT) > -1;
331         maybeShowWfcEmergencyCallingWarning();
332 
333         ViewUtil.setupFloatingActionButton(mDialButton, getResources());
334 
335         if (icicle != null) {
336             super.onRestoreInstanceState(icicle);
337         }
338 
339         // Extract phone number from intent
340         Uri data = getIntent().getData();
341         if (data != null && (PhoneAccount.SCHEME_TEL.equals(data.getScheme()))) {
342             String number = PhoneNumberUtils.getNumberFromIntent(getIntent(), this);
343             if (number != null) {
344                 mDigits.setText(number);
345             }
346         }
347 
348         // if the mToneGenerator creation fails, just continue without it.  It is
349         // a local audio signal, and is not as important as the dtmf tone itself.
350         synchronized (mToneGeneratorLock) {
351             if (mToneGenerator == null) {
352                 try {
353                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
354                 } catch (RuntimeException e) {
355                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
356                     mToneGenerator = null;
357                 }
358             }
359         }
360 
361         final IntentFilter intentFilter = new IntentFilter();
362         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
363         registerReceiver(mBroadcastReceiver, intentFilter);
364 
365         mEmergencyActionGroup = (EmergencyActionGroup) findViewById(R.id.emergency_action_group);
366 
367         mEmergencyInfoGroup = (EmergencyInfoGroup) findViewById(R.id.emergency_info_button);
368 
369         if (mShortcutViewConfig.isEnabled()) {
370             setupEmergencyShortcutsView();
371         }
372     }
373 
374     @Override
onDestroy()375     protected void onDestroy() {
376         super.onDestroy();
377         synchronized (mToneGeneratorLock) {
378             if (mToneGenerator != null) {
379                 mToneGenerator.release();
380                 mToneGenerator = null;
381             }
382         }
383         unregisterReceiver(mBroadcastReceiver);
384         if (mShortcutAdapter != null && mShortcutDataSetObserver != null) {
385             mShortcutAdapter.unregisterDataSetObserver(mShortcutDataSetObserver);
386             mShortcutDataSetObserver = null;
387         }
388     }
389 
390     @Override
onRestoreInstanceState(Bundle icicle)391     protected void onRestoreInstanceState(Bundle icicle) {
392         mLastNumber = icicle.getString(LAST_NUMBER);
393     }
394 
395     @Override
onSaveInstanceState(Bundle outState)396     protected void onSaveInstanceState(Bundle outState) {
397         super.onSaveInstanceState(outState);
398         outState.putString(LAST_NUMBER, mLastNumber);
399     }
400 
401     /**
402      * Explicitly turn off number formatting, since it gets in the way of the emergency
403      * number detector
404      */
maybeAddNumberFormatting()405     protected void maybeAddNumberFormatting() {
406         // Do nothing.
407     }
408 
409     @Override
onPostCreate(Bundle savedInstanceState)410     protected void onPostCreate(Bundle savedInstanceState) {
411         super.onPostCreate(savedInstanceState);
412 
413         // This can't be done in onCreate(), since the auto-restoring of the digits
414         // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
415         // is called. This method will be called every time the activity is created, and
416         // will always happen after onRestoreSavedInstanceState().
417         mDigits.addTextChangedListener(this);
418     }
419 
setupKeypad()420     private void setupKeypad() {
421         // Setup the listeners for the buttons
422         for (int id : DIALER_KEYS) {
423             final DialpadKeyButton key = (DialpadKeyButton) findViewById(id);
424             key.setOnPressedListener(this);
425         }
426 
427         View view = findViewById(R.id.zero);
428         view.setOnLongClickListener(this);
429     }
430 
431     @Override
onBackPressed()432     public void onBackPressed() {
433         // If shortcut view is enabled and Dialpad view is visible, pressing the back key will
434         // back to display EmergencyShortcutView view. Otherwise, it would finish the activity.
435         if (mShortcutViewConfig.isEnabled() && mDialpadView != null
436                 && mDialpadView.getVisibility() == View.VISIBLE) {
437             switchView(mEmergencyShortcutView, mDialpadView, true);
438             return;
439         }
440         super.onBackPressed();
441     }
442 
443     /**
444      * handle key events
445      */
446     @Override
onKeyDown(int keyCode, KeyEvent event)447     public boolean onKeyDown(int keyCode, KeyEvent event) {
448         switch (keyCode) {
449             // Happen when there's a "Call" hard button.
450             case KeyEvent.KEYCODE_CALL: {
451                 if (TextUtils.isEmpty(mDigits.getText().toString())) {
452                     // if we are adding a call from the InCallScreen and the phone
453                     // number entered is empty, we just close the dialer to expose
454                     // the InCallScreen under it.
455                     finish();
456                 } else {
457                     // otherwise, we place the call.
458                     placeCall();
459                 }
460                 return true;
461             }
462         }
463         return super.onKeyDown(keyCode, event);
464     }
465 
keyPressed(int keyCode)466     private void keyPressed(int keyCode) {
467         mDigits.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
468         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
469         mDigits.onKeyDown(keyCode, event);
470     }
471 
472     @Override
onKey(View view, int keyCode, KeyEvent event)473     public boolean onKey(View view, int keyCode, KeyEvent event) {
474         switch (view.getId()) {
475             case R.id.digits:
476                 // Happen when "Done" button of the IME is pressed. This can happen when this
477                 // Activity is forced into landscape mode due to a desk dock.
478                 if (keyCode == KeyEvent.KEYCODE_ENTER
479                         && event.getAction() == KeyEvent.ACTION_UP) {
480                     placeCall();
481                     return true;
482                 }
483                 break;
484         }
485         return false;
486     }
487 
488     @Override
dispatchTouchEvent(MotionEvent ev)489     public boolean dispatchTouchEvent(MotionEvent ev) {
490         onPreTouchEvent(ev);
491         boolean handled = super.dispatchTouchEvent(ev);
492         onPostTouchEvent(ev);
493         return handled;
494     }
495 
496     @Override
onConfirmClick(EmergencyShortcutButton button)497     public void onConfirmClick(EmergencyShortcutButton button) {
498         if (button == null) return;
499         String phoneNumber = button.getPhoneNumber();
500 
501         if (!TextUtils.isEmpty(phoneNumber)) {
502             if (DBG) Log.d(LOG_TAG, "dial emergency number: " + Rlog.pii(LOG_TAG, phoneNumber));
503 
504             // Write metrics when user has intention to make a call from a shortcut button.
505             mMetricsLogger.logPlaceCall(DialedFrom.SHORTCUT, PhoneNumberType.HAS_SHORTCUT,
506                     mShortcutViewConfig.getPhoneInfo());
507 
508             placeCall(phoneNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT,
509                     mShortcutViewConfig.getPhoneInfo());
510         } else {
511             Log.d(LOG_TAG, "emergency number is empty");
512         }
513     }
514 
515     @Override
onConfirmClick(EmergencyInfoGroup button)516     public void onConfirmClick(EmergencyInfoGroup button) {
517         if (button == null) return;
518 
519         Intent intent = (Intent) button.getTag(R.id.tag_intent);
520         if (intent != null) {
521             startActivity(intent);
522         }
523     }
524 
525     @Override
onClick(View view)526     public void onClick(View view) {
527         switch (view.getId()) {
528             case R.id.deleteButton: {
529                 keyPressed(KeyEvent.KEYCODE_DEL);
530                 return;
531             }
532             case R.id.floating_action_button: {
533                 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
534                 placeCall();
535                 return;
536             }
537             case R.id.digits: {
538                 if (mDigits.length() != 0) {
539                     mDigits.setCursorVisible(true);
540                 }
541                 return;
542             }
543             case R.id.floating_action_button_dialpad: {
544                 mDigits.getText().clear();
545                 switchView(mDialpadView, mEmergencyShortcutView, true);
546                 return;
547             }
548         }
549     }
550 
551     @Override
onPressed(View view, boolean pressed)552     public void onPressed(View view, boolean pressed) {
553         if (!pressed) {
554             return;
555         }
556         switch (view.getId()) {
557             case R.id.one: {
558                 playTone(ToneGenerator.TONE_DTMF_1);
559                 keyPressed(KeyEvent.KEYCODE_1);
560                 return;
561             }
562             case R.id.two: {
563                 playTone(ToneGenerator.TONE_DTMF_2);
564                 keyPressed(KeyEvent.KEYCODE_2);
565                 return;
566             }
567             case R.id.three: {
568                 playTone(ToneGenerator.TONE_DTMF_3);
569                 keyPressed(KeyEvent.KEYCODE_3);
570                 return;
571             }
572             case R.id.four: {
573                 playTone(ToneGenerator.TONE_DTMF_4);
574                 keyPressed(KeyEvent.KEYCODE_4);
575                 return;
576             }
577             case R.id.five: {
578                 playTone(ToneGenerator.TONE_DTMF_5);
579                 keyPressed(KeyEvent.KEYCODE_5);
580                 return;
581             }
582             case R.id.six: {
583                 playTone(ToneGenerator.TONE_DTMF_6);
584                 keyPressed(KeyEvent.KEYCODE_6);
585                 return;
586             }
587             case R.id.seven: {
588                 playTone(ToneGenerator.TONE_DTMF_7);
589                 keyPressed(KeyEvent.KEYCODE_7);
590                 return;
591             }
592             case R.id.eight: {
593                 playTone(ToneGenerator.TONE_DTMF_8);
594                 keyPressed(KeyEvent.KEYCODE_8);
595                 return;
596             }
597             case R.id.nine: {
598                 playTone(ToneGenerator.TONE_DTMF_9);
599                 keyPressed(KeyEvent.KEYCODE_9);
600                 return;
601             }
602             case R.id.zero: {
603                 playTone(ToneGenerator.TONE_DTMF_0);
604                 keyPressed(KeyEvent.KEYCODE_0);
605                 return;
606             }
607             case R.id.pound: {
608                 playTone(ToneGenerator.TONE_DTMF_P);
609                 keyPressed(KeyEvent.KEYCODE_POUND);
610                 return;
611             }
612             case R.id.star: {
613                 playTone(ToneGenerator.TONE_DTMF_S);
614                 keyPressed(KeyEvent.KEYCODE_STAR);
615                 return;
616             }
617         }
618     }
619 
620     /**
621      * called for long touch events
622      */
623     @Override
onLongClick(View view)624     public boolean onLongClick(View view) {
625         int id = view.getId();
626         switch (id) {
627             case R.id.deleteButton: {
628                 mDigits.getText().clear();
629                 return true;
630             }
631             case R.id.zero: {
632                 removePreviousDigitIfPossible();
633                 keyPressed(KeyEvent.KEYCODE_PLUS);
634                 return true;
635             }
636         }
637         return false;
638     }
639 
640     @Override
onStart()641     protected void onStart() {
642         super.onStart();
643 
644         mMetricsLogger.logLaunchEmergencyDialer(mEntryType,
645                 mShortcutViewConfig.isEnabled() ? UiModeErrorCode.SUCCESS
646                         : UiModeErrorCode.UNSPECIFIED_ERROR);
647 
648         if (mShortcutViewConfig.isEnabled()) {
649             // Shortcut view doesn't support dark text theme.
650             mBackgroundDrawable.setColor(Color.BLACK, false);
651             updateTheme(false);
652         } else {
653             mColorExtractor.addOnColorsChangedListener(this);
654             GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
655                     ColorExtractor.TYPE_EXTRA_DARK);
656             // Do not animate when view isn't visible yet, just set an initial state.
657             mBackgroundDrawable.setColor(lockScreenColors.getMainColor(), false);
658             updateTheme(lockScreenColors.supportsDarkText());
659         }
660 
661         if (mShortcutViewConfig.isEnabled()) {
662             updateLocationAndEccInfo();
663         }
664     }
665 
666     @Override
onResume()667     protected void onResume() {
668         super.onResume();
669 
670         // retrieve the DTMF tone play back setting.
671         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
672                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
673 
674         // if the mToneGenerator creation fails, just continue without it.  It is
675         // a local audio signal, and is not as important as the dtmf tone itself.
676         synchronized (mToneGeneratorLock) {
677             if (mToneGenerator == null) {
678                 try {
679                     mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
680                             TONE_RELATIVE_VOLUME);
681                 } catch (RuntimeException e) {
682                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
683                     mToneGenerator = null;
684                 }
685             }
686         }
687 
688         updateDialAndDeleteButtonStateEnabledAttr();
689     }
690 
691     @Override
onPause()692     public void onPause() {
693         super.onPause();
694     }
695 
696     @Override
onStop()697     protected void onStop() {
698         super.onStop();
699         mColorExtractor.removeOnColorsChangedListener(this);
700     }
701 
702     /**
703      * Sets theme based on gradient colors
704      *
705      * @param supportsDarkText true if gradient supports dark text
706      */
updateTheme(boolean supportsDarkText)707     private void updateTheme(boolean supportsDarkText) {
708         if (mSupportsDarkText == supportsDarkText) {
709             return;
710         }
711         mSupportsDarkText = supportsDarkText;
712 
713         // We can't change themes after inflation, in this case we'll have to recreate
714         // the whole activity.
715         if (mBackgroundDrawable != null) {
716             recreate();
717             return;
718         }
719 
720         int vis = getWindow().getDecorView().getSystemUiVisibility();
721         if (supportsDarkText) {
722             vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
723             vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
724             setTheme(R.style.EmergencyDialerThemeDark);
725         } else {
726             vis &= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
727             vis &= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
728             setTheme(R.style.EmergencyDialerTheme);
729         }
730         getWindow().getDecorView().setSystemUiVisibility(vis);
731     }
732 
733     /**
734      * place the call, but check to make sure it is a viable number.
735      */
placeCall()736     private void placeCall() {
737         mLastNumber = mDigits.getText().toString();
738 
739         // Convert into emergency number according to emergency conversion map.
740         // If conversion map is not defined (this is default), this method does
741         // nothing and just returns input number.
742         mLastNumber = PhoneNumberUtils.convertToEmergencyNumber(this, mLastNumber);
743 
744         @DialedFrom final int dialedFrom =
745                 mShortcutViewConfig.isEnabled() ? DialedFrom.FASTER_LAUNCHER_DIALPAD
746                         : DialedFrom.TRADITIONAL_DIALPAD;
747         @PhoneNumberType final int phoneNumberType;
748 
749         boolean isEmergencyNumber;
750         ShortcutViewUtils.PhoneInfo phoneToMakeCall = null;
751         if (mShortcutAdapter != null && mShortcutAdapter.hasShortcut(mLastNumber)) {
752             isEmergencyNumber = true;
753             phoneToMakeCall = mShortcutViewConfig.getPhoneInfo();
754             phoneNumberType = PhoneNumberType.HAS_SHORTCUT;
755         } else if (mShortcutViewConfig.hasPromotedEmergencyNumber(mLastNumber)) {
756             // If a number from SIM/network/... is categoried as police/ambulance/fire,
757             // hasPromotedEmergencyNumber() will return true, but it maybe not promoted as a
758             // shortcut button because a number provided by database has higher priority.
759             isEmergencyNumber = true;
760             phoneToMakeCall = mShortcutViewConfig.getPhoneInfo();
761             phoneNumberType = PhoneNumberType.NO_SHORTCUT;
762         } else {
763             isEmergencyNumber = getSystemService(TelephonyManager.class)
764                     .isEmergencyNumber(mLastNumber);
765             phoneNumberType = PhoneNumberType.NO_SHORTCUT;
766         }
767 
768         if (isEmergencyNumber) {
769             if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
770 
771             // place the call if it is a valid number
772             if (mLastNumber == null || !TextUtils.isGraphic(mLastNumber)) {
773                 // There is no number entered.
774                 playTone(ToneGenerator.TONE_PROP_NACK);
775                 return;
776             }
777 
778             // Write metrics when user has intention to make a call from dialpad
779             mMetricsLogger.logPlaceCall(dialedFrom, phoneNumberType, phoneToMakeCall);
780 
781             placeCall(mLastNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD,
782                     phoneToMakeCall);
783         } else {
784             if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
785 
786             // Write metrics when user has intention to make a call of a non-emergency number, even
787             // this number is rejected.
788             mMetricsLogger.logPlaceCall(dialedFrom, PhoneNumberType.NOT_EMERGENCY_NUMBER,
789                     phoneToMakeCall);
790 
791             showDialog(BAD_EMERGENCY_NUMBER_DIALOG);
792         }
793         mDigits.getText().delete(0, mDigits.getText().length());
794     }
795 
placeCall(String number, int callSource, ShortcutViewUtils.PhoneInfo phone)796     private void placeCall(String number, int callSource, ShortcutViewUtils.PhoneInfo phone) {
797         Log.d(LOG_TAG, "Place emergency call from " + callSourceToString(callSource)
798                 + ", entry = " + entryTypeToString(mEntryType));
799 
800         Bundle extras = new Bundle();
801         extras.putInt(TelecomManager.EXTRA_CALL_SOURCE, callSource);
802         /**
803          * This is used for Telecom and Telephony to tell modem user's intent is emergency call,
804          * when the dialed number is ambiguous and identified as both emergency number and any
805          * other non-emergency number; e.g. in some situation, 611 could be both an emergency
806          * number in a country and a non-emergency number of a carrier's customer service hotline.
807          */
808         extras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, true);
809 
810         if (phone != null && phone.getPhoneAccountHandle() != null) {
811             // Requests to dial through the specified phone.
812             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
813                     phone.getPhoneAccountHandle());
814         }
815 
816         TelecomManager tm = this.getSystemService(TelecomManager.class);
817         tm.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras);
818     }
819 
820     /**
821      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
822      *
823      * The tone is played locally, using the audio stream for phone calls.
824      * Tones are played only if the "Audible touch tones" user preference
825      * is checked, and are NOT played if the device is in silent mode.
826      *
827      * @param tone a tone code from {@link ToneGenerator}
828      */
playTone(int tone)829     void playTone(int tone) {
830         // if local tone playback is disabled, just return.
831         if (!mDTMFToneEnabled) {
832             return;
833         }
834 
835         // Also do nothing if the phone is in silent mode.
836         // We need to re-check the ringer mode for *every* playTone()
837         // call, rather than keeping a local flag that's updated in
838         // onResume(), since it's possible to toggle silent mode without
839         // leaving the current activity (via the ENDCALL-longpress menu.)
840         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
841         int ringerMode = audioManager.getRingerMode();
842         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
843                 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
844             return;
845         }
846 
847         synchronized (mToneGeneratorLock) {
848             if (mToneGenerator == null) {
849                 Log.w(LOG_TAG, "playTone: mToneGenerator == null, tone: " + tone);
850                 return;
851             }
852 
853             // Start the new tone (will stop any playing tone)
854             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
855         }
856     }
857 
createErrorMessage(String number)858     private CharSequence createErrorMessage(String number) {
859         if (!TextUtils.isEmpty(number)) {
860             String errorString = getString(R.string.dial_emergency_error, number);
861             int startingPosition = errorString.indexOf(number);
862             int endingPosition = startingPosition + number.length();
863             Spannable result = new SpannableString(errorString);
864             PhoneNumberUtils.addTtsSpan(result, startingPosition, endingPosition);
865             return result;
866         } else {
867             return getText(R.string.dial_emergency_empty_error).toString();
868         }
869     }
870 
871     @Override
onCreateDialog(int id)872     protected Dialog onCreateDialog(int id) {
873         AlertDialog dialog = null;
874         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
875             // construct dialog
876             dialog = new AlertDialog.Builder(this, R.style.EmergencyDialerAlertDialogTheme)
877                     .setTitle(getText(R.string.emergency_enable_radio_dialog_title))
878                     .setMessage(createErrorMessage(mLastNumber))
879                     .setPositiveButton(R.string.ok, null)
880                     .setCancelable(true).create();
881 
882             // blur stuff behind the dialog
883             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
884             setShowWhenLocked(true);
885         }
886         return dialog;
887     }
888 
889     @Override
onPrepareDialog(int id, Dialog dialog)890     protected void onPrepareDialog(int id, Dialog dialog) {
891         super.onPrepareDialog(id, dialog);
892         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
893             AlertDialog alert = (AlertDialog) dialog;
894             alert.setMessage(createErrorMessage(mLastNumber));
895         }
896     }
897 
898     @Override
onOptionsItemSelected(MenuItem item)899     public boolean onOptionsItemSelected(MenuItem item) {
900         final int itemId = item.getItemId();
901         if (itemId == android.R.id.home) {
902             onBackPressed();
903             return true;
904         }
905         return super.onOptionsItemSelected(item);
906     }
907 
908     /**
909      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
910      */
updateDialAndDeleteButtonStateEnabledAttr()911     private void updateDialAndDeleteButtonStateEnabledAttr() {
912         final boolean notEmpty = mDigits.length() != 0;
913 
914         mDelete.setEnabled(notEmpty);
915     }
916 
917     /**
918      * Remove the digit just before the current position. Used by various long pressed callbacks
919      * to remove the digit that was populated as a result of the short click.
920      */
removePreviousDigitIfPossible()921     private void removePreviousDigitIfPossible() {
922         final int currentPosition = mDigits.getSelectionStart();
923         if (currentPosition > 0) {
924             mDigits.setSelection(currentPosition);
925             mDigits.getText().delete(currentPosition - 1, currentPosition);
926         }
927     }
928 
929     /**
930      * Update the text-to-speech annotations in the edit field.
931      */
updateTtsSpans()932     private void updateTtsSpans() {
933         for (Object o : mDigits.getText().getSpans(0, mDigits.getText().length(), TtsSpan.class)) {
934             mDigits.getText().removeSpan(o);
935         }
936         PhoneNumberUtils.ttsSpanAsPhoneNumber(mDigits.getText(), 0, mDigits.getText().length());
937     }
938 
939     @Override
onColorsChanged(ColorExtractor extractor, int which)940     public void onColorsChanged(ColorExtractor extractor, int which) {
941         if ((which & WallpaperManager.FLAG_LOCK) != 0) {
942             GradientColors colors = extractor.getColors(WallpaperManager.FLAG_LOCK,
943                     ColorExtractor.TYPE_EXTRA_DARK);
944             mBackgroundDrawable.setColor(colors.getMainColor(), true /* animated */);
945             updateTheme(colors.supportsDarkText());
946         }
947     }
948 
949     /**
950      * Where a carrier requires a warning that emergency calling is not available while on WFC,
951      * add hint text above the dial pad which warns the user of this case.
952      */
maybeShowWfcEmergencyCallingWarning()953     private void maybeShowWfcEmergencyCallingWarning() {
954         if (!mIsWfcEmergencyCallingWarningEnabled) {
955             Log.i(LOG_TAG, "maybeShowWfcEmergencyCallingWarning: warning disabled by carrier.");
956             return;
957         }
958 
959         // Use an async task rather than calling into Telephony on UI thread.
960         AsyncTask<Void, Void, Boolean> showWfcWarningTask = new AsyncTask<Void, Void, Boolean>() {
961             @Override
962             protected Boolean doInBackground(Void... voids) {
963                 TelephonyManager tm = getSystemService(TelephonyManager.class);
964                 boolean isWfcAvailable = tm.isWifiCallingAvailable();
965                 ServiceState ss = tm.getServiceState();
966                 boolean isCellAvailable =
967                         ss.getRilVoiceRadioTechnology() != RIL_RADIO_TECHNOLOGY_UNKNOWN;
968                 Log.i(LOG_TAG, "showWfcWarningTask: isWfcAvailable=" + isWfcAvailable
969                         + " isCellAvailable=" + isCellAvailable
970                         + "(rat=" + ss.getRilVoiceRadioTechnology() + ")");
971                 return isWfcAvailable && !isCellAvailable;
972             }
973 
974             @Override
975             protected void onPostExecute(Boolean result) {
976                 if (result.booleanValue()) {
977                     Log.i(LOG_TAG, "showWfcWarningTask: showing ecall warning");
978                     mDigits.setHint(R.string.dial_emergency_calling_not_available);
979                 } else {
980                     Log.i(LOG_TAG, "showWfcWarningTask: hiding ecall warning");
981                     mDigits.setHint("");
982                 }
983                 maybeChangeHintSize();
984             }
985         };
986         showWfcWarningTask.execute((Void) null);
987     }
988 
989     /**
990      * Where a hint is applied and there are no digits dialed, disable autoresize of the dial digits
991      * edit view and set the font size to a smaller size appropriate for the emergency calling
992      * warning.
993      */
maybeChangeHintSize()994     private void maybeChangeHintSize() {
995         if (TextUtils.isEmpty(mDigits.getHint())
996                 || !TextUtils.isEmpty(mDigits.getText().toString())) {
997             // No hint or there are dialed digits, so use default size.
998             mDigits.setTextSize(TypedValue.COMPLEX_UNIT_SP, mDefaultDigitsTextSize);
999             // By default, the digits view auto-resizes to fit the text it contains, so
1000             // enable that now.
1001             mDigits.setResizeEnabled(true);
1002             Log.i(LOG_TAG, "no hint - setting to " + mDigits.getScaledTextSize());
1003         } else {
1004             // Hint present and no dialed digits, set custom font size appropriate for the warning.
1005             mDigits.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(
1006                     R.dimen.emergency_call_warning_size));
1007             // Since we're populating this with a static text string, disable auto-resize.
1008             mDigits.setResizeEnabled(false);
1009             Log.i(LOG_TAG, "hint - setting to " + mDigits.getScaledTextSize());
1010         }
1011     }
1012 
setupEmergencyShortcutsView()1013     private void setupEmergencyShortcutsView() {
1014         mEmergencyShortcutView = findViewById(R.id.emergency_dialer_shortcuts);
1015         mDialpadView = findViewById(R.id.emergency_dialer);
1016 
1017         mEmergencyShortcutView.setAccessibilityDelegate(mAccessibilityDelegate);
1018         mDialpadView.setAccessibilityDelegate(mAccessibilityDelegate);
1019 
1020         final View dialpadButton = findViewById(R.id.floating_action_button_dialpad);
1021         dialpadButton.setOnClickListener(this);
1022 
1023         mEmergencyInfoGroup.setOnConfirmClickListener(this);
1024 
1025         mEmergencyShortcutButtonList = new ArrayList<>();
1026         setupEmergencyCallShortcutButton();
1027 
1028         updateLocationAndEccInfo();
1029 
1030         switchView(mEmergencyShortcutView, mDialpadView, false);
1031     }
1032 
setLocationInfo()1033     private void setLocationInfo() {
1034         final View locationInfo = findViewById(R.id.location_info);
1035 
1036         String countryIso = mShortcutViewConfig.getCountryIso();
1037         String countryName = null;
1038         if (!TextUtils.isEmpty(countryIso)) {
1039             Locale locale = Locale.getDefault();
1040             countryName = new Locale(locale.getLanguage(), countryIso, locale.getVariant())
1041                     .getDisplayCountry();
1042         }
1043         if (TextUtils.isEmpty(countryName)) {
1044             locationInfo.setVisibility(View.INVISIBLE);
1045         } else {
1046             final TextView location = (TextView) locationInfo.findViewById(R.id.location_text);
1047             location.setText(countryName);
1048             locationInfo.setVisibility(View.VISIBLE);
1049         }
1050     }
1051 
setupEmergencyCallShortcutButton()1052     private void setupEmergencyCallShortcutButton() {
1053         final ViewGroup shortcutButtonContainer = findViewById(
1054                 R.id.emergency_shortcut_buttons_container);
1055         shortcutButtonContainer.setClipToOutline(true);
1056         final TextView emergencyNumberTitle = findViewById(R.id.emergency_number_title);
1057 
1058         mShortcutAdapter = new EccShortcutAdapter(this) {
1059             @Override
1060             public View inflateView(View convertView, ViewGroup parent, CharSequence number,
1061                     CharSequence description, int iconRes) {
1062                 EmergencyShortcutButton button = (EmergencyShortcutButton) getLayoutInflater()
1063                         .inflate(R.layout.emergency_shortcut_button, parent, false);
1064                 button.setPhoneNumber(number);
1065                 button.setPhoneDescription(description);
1066                 button.setPhoneTypeIcon(iconRes);
1067                 button.setOnConfirmClickListener(EmergencyDialer.this);
1068                 return button;
1069             }
1070         };
1071         mShortcutDataSetObserver = new DataSetObserver() {
1072             @Override
1073             public void onChanged() {
1074                 super.onChanged();
1075                 updateLayout();
1076             }
1077 
1078             @Override
1079             public void onInvalidated() {
1080                 super.onInvalidated();
1081                 updateLayout();
1082             }
1083 
1084             private void updateLayout() {
1085                 // clear previous added buttons
1086                 shortcutButtonContainer.removeAllViews();
1087                 mEmergencyShortcutButtonList.clear();
1088 
1089                 for (int i = 0; i < mShortcutAdapter.getCount() && i < SHORTCUT_SIZE_LIMIT; ++i) {
1090                     EmergencyShortcutButton button = (EmergencyShortcutButton)
1091                             mShortcutAdapter.getView(i, null, shortcutButtonContainer);
1092                     mEmergencyShortcutButtonList.add(button);
1093                     shortcutButtonContainer.addView(button);
1094                 }
1095 
1096                 // Update emergency numbers title for numerous buttons.
1097                 if (mEmergencyShortcutButtonList.size() > 1) {
1098                     emergencyNumberTitle.setText(getString(
1099                             R.string.numerous_emergency_numbers_title));
1100                     // Update mEmergencyInfoGroup margin to avoid UI overlay when
1101                     // emergency shortcut button more than 2.
1102                     if (mEmergencyShortcutButtonList.size() > 2) {
1103                         mEmergencyInfoGroup.updateLayoutMargin();
1104                     }
1105                 } else {
1106                     emergencyNumberTitle.setText(getText(R.string.single_emergency_number_title));
1107                 }
1108             }
1109         };
1110         mShortcutAdapter.registerDataSetObserver(mShortcutDataSetObserver);
1111     }
1112 
updateLocationAndEccInfo()1113     private void updateLocationAndEccInfo() {
1114         if (!isFinishing() && !isDestroyed()) {
1115             setLocationInfo();
1116             if (mShortcutAdapter != null) {
1117                 mShortcutAdapter.updateCountryEccInfo(this, mShortcutViewConfig.getPhoneInfo());
1118             }
1119         }
1120     }
1121 
1122     /**
1123      * Called by the activity before a touch event is dispatched to the view hierarchy.
1124      */
onPreTouchEvent(MotionEvent event)1125     private void onPreTouchEvent(MotionEvent event) {
1126         mEmergencyActionGroup.onPreTouchEvent(event);
1127         mEmergencyInfoGroup.onPreTouchEvent(event);
1128 
1129         if (mEmergencyShortcutButtonList != null) {
1130             for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) {
1131                 button.onPreTouchEvent(event);
1132             }
1133         }
1134     }
1135 
1136     /**
1137      * Called by the activity after a touch event is dispatched to the view hierarchy.
1138      */
onPostTouchEvent(MotionEvent event)1139     private void onPostTouchEvent(MotionEvent event) {
1140         mEmergencyActionGroup.onPostTouchEvent(event);
1141         mEmergencyInfoGroup.onPostTouchEvent(event);
1142 
1143         if (mEmergencyShortcutButtonList != null) {
1144             for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) {
1145                 button.onPostTouchEvent(event);
1146             }
1147         }
1148     }
1149 
1150     /**
1151      * Switch two view.
1152      *
1153      * @param displayView  the view would be displayed.
1154      * @param hideView     the view would be hidden.
1155      * @param hasAnimation is {@code true} when the view should be displayed with animation.
1156      */
switchView(View displayView, View hideView, boolean hasAnimation)1157     private void switchView(View displayView, View hideView, boolean hasAnimation) {
1158         if (displayView == null || hideView == null) {
1159             return;
1160         }
1161 
1162         if (displayView.getVisibility() == View.VISIBLE) {
1163             return;
1164         }
1165 
1166         if (hasAnimation) {
1167             crossfade(hideView, displayView);
1168         } else {
1169             hideView.setVisibility(View.GONE);
1170             displayView.setVisibility(View.VISIBLE);
1171         }
1172     }
1173 
1174     /**
1175      * Fade out and fade in animation between two view transition.
1176      */
crossfade(View fadeOutView, View fadeInView)1177     private void crossfade(View fadeOutView, View fadeInView) {
1178         if (fadeOutView == null || fadeInView == null) {
1179             return;
1180         }
1181         final int shortAnimationDuration = getResources().getInteger(
1182                 android.R.integer.config_shortAnimTime);
1183 
1184         fadeInView.setAlpha(0f);
1185         fadeInView.setVisibility(View.VISIBLE);
1186 
1187         fadeInView.animate()
1188                 .alpha(1f)
1189                 .setDuration(shortAnimationDuration)
1190                 .setListener(null);
1191 
1192         fadeOutView.animate()
1193                 .alpha(0f)
1194                 .setDuration(shortAnimationDuration)
1195                 .setListener(new AnimatorListenerAdapter() {
1196                     @Override
1197                     public void onAnimationEnd(Animator animation) {
1198                         fadeOutView.setVisibility(View.GONE);
1199                     }
1200                 });
1201     }
1202 
isShortcutNumber(String number)1203     private boolean isShortcutNumber(String number) {
1204         if (TextUtils.isEmpty(number) || mEmergencyShortcutButtonList == null) {
1205             return false;
1206         }
1207 
1208         boolean isShortcut = false;
1209         for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) {
1210             if (button != null && number.equals(button.getPhoneNumber())) {
1211                 isShortcut = true;
1212                 break;
1213             }
1214         }
1215         return isShortcut;
1216     }
1217 
entryTypeToString(int entryType)1218     private String entryTypeToString(int entryType) {
1219         switch (entryType) {
1220             case ENTRY_TYPE_LOCKSCREEN_BUTTON:
1221                 return "LockScreen";
1222             case ENTRY_TYPE_POWER_MENU:
1223                 return "PowerMenu";
1224             default:
1225                 return "Unknown-" + entryType;
1226         }
1227     }
1228 
callSourceToString(int callSource)1229     private String callSourceToString(int callSource) {
1230         switch (callSource) {
1231             case ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD:
1232                 return "DialPad";
1233             case ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT:
1234                 return "Shortcut";
1235             default:
1236                 return "Unknown-" + callSource;
1237         }
1238     }
1239 }
1240