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