1 /*
2  * Copyright (C) 2015 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.contacts.dialog;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.preference.PreferenceManager;
28 import android.telecom.PhoneAccount;
29 import android.telecom.PhoneAccountHandle;
30 import android.telecom.TelecomManager;
31 import android.text.Editable;
32 import android.text.InputFilter;
33 import android.text.TextUtils;
34 import android.text.TextWatcher;
35 import android.util.Log;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewTreeObserver;
39 import android.view.inputmethod.InputMethodManager;
40 import android.widget.AdapterView;
41 import android.widget.ArrayAdapter;
42 import android.widget.EditText;
43 import android.widget.ListView;
44 import android.widget.QuickContactBadge;
45 import android.widget.TextView;
46 
47 import com.android.contacts.CallUtil;
48 import com.android.contacts.ContactPhotoManager;
49 import com.android.contacts.R;
50 import com.android.contacts.compat.CompatUtils;
51 import com.android.contacts.compat.PhoneAccountSdkCompat;
52 import com.android.contacts.compat.telecom.TelecomManagerCompat;
53 import com.android.contacts.util.UriUtils;
54 import com.android.phone.common.animation.AnimUtils;
55 
56 import java.nio.charset.Charset;
57 import java.util.ArrayList;
58 import java.util.List;
59 
60 /**
61  * Implements a dialog which prompts for a call subject for an outgoing call.  The dialog includes
62  * a pop up list of historical call subjects.
63  */
64 public class CallSubjectDialog extends Activity {
65     private static final String TAG = "CallSubjectDialog";
66     private static final int CALL_SUBJECT_LIMIT = 16;
67     private static final int CALL_SUBJECT_HISTORY_SIZE = 5;
68 
69     private static final int REQUEST_SUBJECT = 1001;
70 
71     public static final String PREF_KEY_SUBJECT_HISTORY_COUNT = "subject_history_count";
72     public static final String PREF_KEY_SUBJECT_HISTORY_ITEM = "subject_history_item";
73 
74     /**
75      * Activity intent argument bundle keys:
76      */
77     public static final String ARG_PHOTO_ID = "PHOTO_ID";
78     public static final String ARG_PHOTO_URI = "PHOTO_URI";
79     public static final String ARG_CONTACT_URI = "CONTACT_URI";
80     public static final String ARG_NAME_OR_NUMBER = "NAME_OR_NUMBER";
81     public static final String ARG_IS_BUSINESS = "IS_BUSINESS";
82     public static final String ARG_NUMBER = "NUMBER";
83     public static final String ARG_DISPLAY_NUMBER = "DISPLAY_NUMBER";
84     public static final String ARG_NUMBER_LABEL = "NUMBER_LABEL";
85     public static final String ARG_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
86 
87     private int mAnimationDuration;
88     private Charset mMessageEncoding;
89     private View mBackgroundView;
90     private View mDialogView;
91     private QuickContactBadge mContactPhoto;
92     private TextView mNameView;
93     private TextView mNumberView;
94     private EditText mCallSubjectView;
95     private TextView mCharacterLimitView;
96     private View mHistoryButton;
97     private View mSendAndCallButton;
98     private ListView mSubjectList;
99 
100     private int mLimit = CALL_SUBJECT_LIMIT;
101     private int mPhotoSize;
102     private SharedPreferences mPrefs;
103     private List<String> mSubjectHistory;
104 
105     private long mPhotoID;
106     private Uri mPhotoUri;
107     private Uri mContactUri;
108     private String mNameOrNumber;
109     private boolean mIsBusiness;
110     private String mNumber;
111     private String mDisplayNumber;
112     private String mNumberLabel;
113     private PhoneAccountHandle mPhoneAccountHandle;
114 
115     /**
116      * Handles changes to the text in the subject box.  Ensures the character limit is updated.
117      */
118     private final TextWatcher mTextWatcher = new TextWatcher() {
119         @Override
120         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
121             // no-op
122         }
123 
124         @Override
125         public void onTextChanged(CharSequence s, int start, int before, int count) {
126             updateCharacterLimit();
127         }
128 
129         @Override
130         public void afterTextChanged(Editable s) {
131             // no-op
132         }
133     };
134 
135     /**
136      * Click listener which handles user clicks outside of the dialog.
137      */
138     private View.OnClickListener mBackgroundListener = new View.OnClickListener() {
139         @Override
140         public void onClick(View v) {
141             finish();
142         }
143     };
144 
145     /**
146      * Handles displaying the list of past call subjects.
147      */
148     private final View.OnClickListener mHistoryOnClickListener = new View.OnClickListener() {
149         @Override
150         public void onClick(View v) {
151             hideSoftKeyboard(CallSubjectDialog.this, mCallSubjectView);
152             showCallHistory(mSubjectList.getVisibility() == View.GONE);
153         }
154     };
155 
156     /**
157      * Handles starting a call with a call subject specified.
158      */
159     private final View.OnClickListener mSendAndCallOnClickListener = new View.OnClickListener() {
160         @Override
161         public void onClick(View v) {
162             String subject = mCallSubjectView.getText().toString();
163             Intent intent = CallUtil.getCallWithSubjectIntent(mNumber, mPhoneAccountHandle,
164                     subject);
165 
166             TelecomManagerCompat.placeCall(
167                     CallSubjectDialog.this,
168                     (TelecomManager) getSystemService(Context.TELECOM_SERVICE),
169                     intent);
170 
171             mSubjectHistory.add(subject);
172             saveSubjectHistory(mSubjectHistory);
173             finish();
174         }
175     };
176 
177     /**
178      * Handles auto-hiding the call history when user clicks in the call subject field to give it
179      * focus.
180      */
181     private final View.OnClickListener mCallSubjectClickListener = new View.OnClickListener() {
182         @Override
183         public void onClick(View v) {
184             if (mSubjectList.getVisibility() == View.VISIBLE) {
185                 showCallHistory(false);
186             }
187         }
188     };
189 
190     /**
191      * Item click listener which handles user clicks on the items in the list view.  Dismisses
192      * the activity, returning the subject to the caller and closing the activity with the
193      * {@link Activity#RESULT_OK} result code.
194      */
195     private AdapterView.OnItemClickListener mItemClickListener =
196             new AdapterView.OnItemClickListener() {
197                 @Override
198                 public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) {
199                     mCallSubjectView.setText(mSubjectHistory.get(position));
200                     showCallHistory(false);
201                 }
202             };
203 
204     /**
205      * Show the call subject dialog given a phone number to dial (e.g. from the dialpad).
206      *
207      * @param activity The activity.
208      * @param number The number to dial.
209      */
start(Activity activity, String number)210     public static void start(Activity activity, String number) {
211         start(activity,
212                 -1 /* photoId */,
213                 null /* photoUri */,
214                 null /* contactUri */,
215                 number /* nameOrNumber */,
216                 false /* isBusiness */,
217                 number /* number */,
218                 null /* displayNumber */,
219                 null /* numberLabel */,
220                 null /* phoneAccountHandle */);
221     }
222 
223     /**
224      * Creates a call subject dialog.
225      *
226      * @param activity The current activity.
227      * @param photoId The photo ID (used to populate contact photo).
228      * @param photoUri The photo Uri (used to populate contact photo).
229      * @param contactUri The Contact URI (used so quick contact can be invoked from contact photo).
230      * @param nameOrNumber The name or number of the callee.
231      * @param isBusiness {@code true} if a business is being called (used for contact photo).
232      * @param number The raw number to dial.
233      * @param displayNumber The number to dial, formatted for display.
234      * @param numberLabel The label for the number (if from a contact).
235      * @param phoneAccountHandle The phone account handle.
236      */
start(Activity activity, long photoId, Uri photoUri, Uri contactUri, String nameOrNumber, boolean isBusiness, String number, String displayNumber, String numberLabel, PhoneAccountHandle phoneAccountHandle)237     public static void start(Activity activity, long photoId, Uri photoUri, Uri contactUri,
238             String nameOrNumber, boolean isBusiness, String number, String displayNumber,
239             String numberLabel, PhoneAccountHandle phoneAccountHandle) {
240         Bundle arguments = new Bundle();
241         arguments.putLong(ARG_PHOTO_ID, photoId);
242         arguments.putParcelable(ARG_PHOTO_URI, photoUri);
243         arguments.putParcelable(ARG_CONTACT_URI, contactUri);
244         arguments.putString(ARG_NAME_OR_NUMBER, nameOrNumber);
245         arguments.putBoolean(ARG_IS_BUSINESS, isBusiness);
246         arguments.putString(ARG_NUMBER, number);
247         arguments.putString(ARG_DISPLAY_NUMBER, displayNumber);
248         arguments.putString(ARG_NUMBER_LABEL, numberLabel);
249         arguments.putParcelable(ARG_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
250         start(activity, arguments);
251     }
252 
253     /**
254      * Shows the call subject dialog given a Bundle containing all the arguments required to
255      * display the dialog (e.g. from Quick Contacts).
256      *
257      * @param activity The activity.
258      * @param arguments The arguments bundle.
259      */
start(Activity activity, Bundle arguments)260     public static void start(Activity activity, Bundle arguments) {
261         Intent intent = new Intent(activity, CallSubjectDialog.class);
262         intent.putExtras(arguments);
263         activity.startActivity(intent);
264     }
265 
266     /**
267      * Creates the dialog, inflating the layout and populating it with the name and phone number.
268      *
269      * @param savedInstanceState The last saved instance state of the Fragment,
270      * or null if this is a freshly created Fragment.
271      *
272      * @return Dialog instance.
273      */
274     @Override
onCreate(Bundle savedInstanceState)275     public void onCreate(Bundle savedInstanceState) {
276         super.onCreate(savedInstanceState);
277         mAnimationDuration = getResources().getInteger(R.integer.call_subject_animation_duration);
278         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
279         mPhotoSize = getResources().getDimensionPixelSize(
280                 R.dimen.call_subject_dialog_contact_photo_size);
281         readArguments();
282         loadConfiguration();
283         mSubjectHistory = loadSubjectHistory(mPrefs);
284 
285         setContentView(R.layout.dialog_call_subject);
286         getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
287                 ViewGroup.LayoutParams.MATCH_PARENT);
288         mBackgroundView = findViewById(R.id.call_subject_dialog);
289         mBackgroundView.setOnClickListener(mBackgroundListener);
290         mDialogView = findViewById(R.id.dialog_view);
291         mContactPhoto = (QuickContactBadge) findViewById(R.id.contact_photo);
292         mNameView = (TextView) findViewById(R.id.name);
293         mNumberView = (TextView) findViewById(R.id.number);
294         mCallSubjectView = (EditText) findViewById(R.id.call_subject);
295         mCallSubjectView.addTextChangedListener(mTextWatcher);
296         mCallSubjectView.setOnClickListener(mCallSubjectClickListener);
297         InputFilter[] filters = new InputFilter[1];
298         filters[0] = new InputFilter.LengthFilter(mLimit);
299         mCallSubjectView.setFilters(filters);
300         mCharacterLimitView = (TextView) findViewById(R.id.character_limit);
301         mHistoryButton = findViewById(R.id.history_button);
302         mHistoryButton.setOnClickListener(mHistoryOnClickListener);
303         mHistoryButton.setVisibility(mSubjectHistory.isEmpty() ? View.GONE : View.VISIBLE);
304         mSendAndCallButton = findViewById(R.id.send_and_call_button);
305         mSendAndCallButton.setOnClickListener(mSendAndCallOnClickListener);
306         mSubjectList = (ListView) findViewById(R.id.subject_list);
307         mSubjectList.setOnItemClickListener(mItemClickListener);
308         mSubjectList.setVisibility(View.GONE);
309 
310         updateContactInfo();
311         updateCharacterLimit();
312     }
313 
314     /**
315      * Populates the contact info fields based on the current contact information.
316      */
updateContactInfo()317     private void updateContactInfo() {
318         if (mContactUri != null) {
319             setPhoto(mPhotoID, mPhotoUri, mContactUri, mNameOrNumber, mIsBusiness);
320         } else {
321             mContactPhoto.setVisibility(View.GONE);
322         }
323         mNameView.setText(mNameOrNumber);
324         if (!TextUtils.isEmpty(mNumberLabel) && !TextUtils.isEmpty(mDisplayNumber)) {
325             mNumberView.setVisibility(View.VISIBLE);
326             mNumberView.setText(getString(R.string.call_subject_type_and_number,
327                     mNumberLabel, mDisplayNumber));
328         } else {
329             mNumberView.setVisibility(View.GONE);
330             mNumberView.setText(null);
331         }
332     }
333 
334     /**
335      * Reads arguments from the fragment arguments and populates the necessary instance variables.
336      */
readArguments()337     private void readArguments() {
338         Bundle arguments = getIntent().getExtras();
339         if (arguments == null) {
340             Log.e(TAG, "Arguments cannot be null.");
341             return;
342         }
343         mPhotoID = arguments.getLong(ARG_PHOTO_ID);
344         mPhotoUri = arguments.getParcelable(ARG_PHOTO_URI);
345         mContactUri = arguments.getParcelable(ARG_CONTACT_URI);
346         mNameOrNumber = arguments.getString(ARG_NAME_OR_NUMBER);
347         mIsBusiness = arguments.getBoolean(ARG_IS_BUSINESS);
348         mNumber = arguments.getString(ARG_NUMBER);
349         mDisplayNumber = arguments.getString(ARG_DISPLAY_NUMBER);
350         mNumberLabel = arguments.getString(ARG_NUMBER_LABEL);
351         mPhoneAccountHandle = arguments.getParcelable(ARG_PHONE_ACCOUNT_HANDLE);
352     }
353 
354     /**
355      * Updates the character limit display, coloring the text RED when the limit is reached or
356      * exceeded.
357      */
updateCharacterLimit()358     private void updateCharacterLimit() {
359         String subjectText = mCallSubjectView.getText().toString();
360         final int length;
361 
362         // If a message encoding is specified, use that to count bytes in the message.
363         if (mMessageEncoding != null) {
364             length = subjectText.getBytes(mMessageEncoding).length;
365         } else {
366             // No message encoding specified, so just count characters entered.
367             length = subjectText.length();
368         }
369 
370         mCharacterLimitView.setText(
371                 getString(R.string.call_subject_limit, length, mLimit));
372         if (length >= mLimit) {
373             mCharacterLimitView.setTextColor(getResources().getColor(
374                     R.color.call_subject_limit_exceeded));
375         } else {
376             mCharacterLimitView.setTextColor(getResources().getColor(
377                     R.color.dialtacts_secondary_text_color));
378         }
379     }
380 
381     /**
382      * Sets the photo on the quick contact photo.
383      *
384      * @param photoId
385      * @param photoUri
386      * @param contactUri
387      * @param displayName
388      * @param isBusiness
389      */
setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName, boolean isBusiness)390     private void setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName,
391             boolean isBusiness) {
392         mContactPhoto.assignContactUri(contactUri);
393         if (CompatUtils.isLollipopCompatible()) {
394             mContactPhoto.setOverlay(null);
395         }
396 
397         int contactType;
398         if (isBusiness) {
399             contactType = ContactPhotoManager.TYPE_BUSINESS;
400         } else {
401             contactType = ContactPhotoManager.TYPE_DEFAULT;
402         }
403 
404         String lookupKey = null;
405         if (contactUri != null) {
406             lookupKey = UriUtils.getLookupKeyFromUri(contactUri);
407         }
408 
409         ContactPhotoManager.DefaultImageRequest
410                 request = new ContactPhotoManager.DefaultImageRequest(
411                 displayName, lookupKey, contactType, true /* isCircular */);
412 
413         if (photoId == 0 && photoUri != null) {
414             ContactPhotoManager.getInstance(this).loadPhoto(mContactPhoto, photoUri,
415                     mPhotoSize, false /* darkTheme */, true /* isCircular */, request);
416         } else {
417             ContactPhotoManager.getInstance(this).loadThumbnail(mContactPhoto, photoId,
418                     false /* darkTheme */, true /* isCircular */, request);
419         }
420     }
421 
422     /**
423      * Loads the subject history from shared preferences.
424      *
425      * @param prefs Shared preferences.
426      * @return List of subject history strings.
427      */
loadSubjectHistory(SharedPreferences prefs)428     public static List<String> loadSubjectHistory(SharedPreferences prefs) {
429         int historySize = prefs.getInt(PREF_KEY_SUBJECT_HISTORY_COUNT, 0);
430         List<String> subjects = new ArrayList(historySize);
431 
432         for (int ix = 0 ; ix < historySize; ix++) {
433             String historyItem = prefs.getString(PREF_KEY_SUBJECT_HISTORY_ITEM + ix, null);
434             if (!TextUtils.isEmpty(historyItem)) {
435                 subjects.add(historyItem);
436             }
437         }
438 
439         return subjects;
440     }
441 
442     /**
443      * Saves the subject history list to shared prefs, removing older items so that there are only
444      * {@link #CALL_SUBJECT_HISTORY_SIZE} items at most.
445      *
446      * @param history The history.
447      */
saveSubjectHistory(List<String> history)448     private void saveSubjectHistory(List<String> history) {
449         // Remove oldest subject(s).
450         while (history.size() > CALL_SUBJECT_HISTORY_SIZE) {
451             history.remove(0);
452         }
453 
454         SharedPreferences.Editor editor = mPrefs.edit();
455         int historyCount = 0;
456         for (String subject : history) {
457             if (!TextUtils.isEmpty(subject)) {
458                 editor.putString(PREF_KEY_SUBJECT_HISTORY_ITEM + historyCount,
459                         subject);
460                 historyCount++;
461             }
462         }
463         editor.putInt(PREF_KEY_SUBJECT_HISTORY_COUNT, historyCount);
464         editor.apply();
465     }
466 
467     /**
468      * Hide software keyboard for the given {@link View}.
469      */
hideSoftKeyboard(Context context, View view)470     public void hideSoftKeyboard(Context context, View view) {
471         InputMethodManager imm = (InputMethodManager) context.getSystemService(
472                 Context.INPUT_METHOD_SERVICE);
473         if (imm != null) {
474             imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
475         }
476     }
477 
478     /**
479      * Hides or shows the call history list.
480      *
481      * @param show {@code true} if the call history should be shown, {@code false} otherwise.
482      */
showCallHistory(final boolean show)483     private void showCallHistory(final boolean show) {
484         // Bail early if the visibility has not changed.
485         if ((show && mSubjectList.getVisibility() == View.VISIBLE) ||
486                 (!show && mSubjectList.getVisibility() == View.GONE)) {
487             return;
488         }
489 
490         final int dialogStartingBottom = mDialogView.getBottom();
491         if (show) {
492             // Showing the subject list; bind the list of history items to the list and show it.
493             ArrayAdapter<String> adapter = new ArrayAdapter<String>(CallSubjectDialog.this,
494                     R.layout.call_subject_history_list_item, mSubjectHistory);
495             mSubjectList.setAdapter(adapter);
496             mSubjectList.setVisibility(View.VISIBLE);
497         } else {
498             // Hiding the subject list.
499             mSubjectList.setVisibility(View.GONE);
500         }
501 
502         // Use a ViewTreeObserver so that we can animate between the pre-layout and post-layout
503         // states.
504         final ViewTreeObserver observer = mBackgroundView.getViewTreeObserver();
505         observer.addOnPreDrawListener(
506                 new ViewTreeObserver.OnPreDrawListener() {
507                     @Override
508                     public boolean onPreDraw() {
509                         // We don't want to continue getting called.
510                         if (observer.isAlive()) {
511                             observer.removeOnPreDrawListener(this);
512                         }
513 
514                         // Determine the amount the dialog has shifted due to the relayout.
515                         int shiftAmount = dialogStartingBottom - mDialogView.getBottom();
516 
517                         // If the dialog needs to be shifted, do that now.
518                         if (shiftAmount != 0) {
519                             // Start animation in translated state and animate to translationY 0.
520                             mDialogView.setTranslationY(shiftAmount);
521                             mDialogView.animate()
522                                     .translationY(0)
523                                     .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
524                                     .setDuration(mAnimationDuration)
525                                     .start();
526                         }
527 
528                         if (show) {
529                             // Show the subhect list.
530                             mSubjectList.setTranslationY(mSubjectList.getHeight());
531 
532                             mSubjectList.animate()
533                                     .translationY(0)
534                                     .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
535                                     .setDuration(mAnimationDuration)
536                                     .setListener(new AnimatorListenerAdapter() {
537                                         @Override
538                                         public void onAnimationEnd(Animator animation) {
539                                             super.onAnimationEnd(animation);
540                                         }
541 
542                                         @Override
543                                         public void onAnimationStart(Animator animation) {
544                                             super.onAnimationStart(animation);
545                                             mSubjectList.setVisibility(View.VISIBLE);
546                                         }
547                                     })
548                                     .start();
549                         } else {
550                             // Hide the subject list.
551                             mSubjectList.setTranslationY(0);
552 
553                             mSubjectList.animate()
554                                     .translationY(mSubjectList.getHeight())
555                                     .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
556                                     .setDuration(mAnimationDuration)
557                                     .setListener(new AnimatorListenerAdapter() {
558                                         @Override
559                                         public void onAnimationEnd(Animator animation) {
560                                             super.onAnimationEnd(animation);
561                                             mSubjectList.setVisibility(View.GONE);
562                                         }
563 
564                                         @Override
565                                         public void onAnimationStart(Animator animation) {
566                                             super.onAnimationStart(animation);
567                                         }
568                                     })
569                                     .start();
570                         }
571                         return true;
572                     }
573                 }
574         );
575     }
576 
577     /**
578      * Loads the message encoding and maximum message length from the phone account extras for the
579      * current phone account.
580      */
loadConfiguration()581     private void loadConfiguration() {
582         // Only attempt to load configuration from the phone account extras if the SDK is N or
583         // later.  If we've got a prior SDK the default encoding and message length will suffice.
584         int sdk = android.os.Build.VERSION.SDK_INT;
585         if(sdk <= android.os.Build.VERSION_CODES.M) {
586             return;
587         }
588 
589         if (mPhoneAccountHandle == null) {
590             return;
591         }
592 
593         TelecomManager telecomManager =
594                 (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
595         final PhoneAccount account = telecomManager.getPhoneAccount(mPhoneAccountHandle);
596 
597         Bundle phoneAccountExtras = PhoneAccountSdkCompat.getExtras(account);
598         if (phoneAccountExtras == null) {
599             return;
600         }
601 
602         // Get limit, if provided; otherwise default to existing value.
603         mLimit = phoneAccountExtras
604                 .getInt(PhoneAccountSdkCompat.EXTRA_CALL_SUBJECT_MAX_LENGTH, mLimit);
605 
606         // Get charset; default to none (e.g. count characters 1:1).
607         String charsetName = phoneAccountExtras.getString(
608                 PhoneAccountSdkCompat.EXTRA_CALL_SUBJECT_CHARACTER_ENCODING);
609 
610         if (!TextUtils.isEmpty(charsetName)) {
611             try {
612                 mMessageEncoding = Charset.forName(charsetName);
613             } catch (java.nio.charset.UnsupportedCharsetException uce) {
614                 // Character set was invalid; log warning and fallback to none.
615                 Log.w(TAG, "Invalid charset: " + charsetName);
616                 mMessageEncoding = null;
617             }
618         } else {
619             // No character set specified, so count characters 1:1.
620             mMessageEncoding = null;
621         }
622     }
623 }
624