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         getWindow().setHideOverlayWindows(true);
278         mAnimationDuration = getResources().getInteger(R.integer.call_subject_animation_duration);
279         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
280         mPhotoSize = getResources().getDimensionPixelSize(
281                 R.dimen.call_subject_dialog_contact_photo_size);
282         readArguments();
283         loadConfiguration();
284         mSubjectHistory = loadSubjectHistory(mPrefs);
285 
286         setContentView(R.layout.dialog_call_subject);
287         getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
288                 ViewGroup.LayoutParams.MATCH_PARENT);
289         mBackgroundView = findViewById(R.id.call_subject_dialog);
290         mBackgroundView.setOnClickListener(mBackgroundListener);
291         mDialogView = findViewById(R.id.dialog_view);
292         mContactPhoto = (QuickContactBadge) findViewById(R.id.contact_photo);
293         mNameView = (TextView) findViewById(R.id.name);
294         mNumberView = (TextView) findViewById(R.id.number);
295         mCallSubjectView = (EditText) findViewById(R.id.call_subject);
296         mCallSubjectView.addTextChangedListener(mTextWatcher);
297         mCallSubjectView.setOnClickListener(mCallSubjectClickListener);
298         InputFilter[] filters = new InputFilter[1];
299         filters[0] = new InputFilter.LengthFilter(mLimit);
300         mCallSubjectView.setFilters(filters);
301         mCharacterLimitView = (TextView) findViewById(R.id.character_limit);
302         mHistoryButton = findViewById(R.id.history_button);
303         mHistoryButton.setOnClickListener(mHistoryOnClickListener);
304         mHistoryButton.setVisibility(mSubjectHistory.isEmpty() ? View.GONE : View.VISIBLE);
305         mSendAndCallButton = findViewById(R.id.send_and_call_button);
306         mSendAndCallButton.setOnClickListener(mSendAndCallOnClickListener);
307         mSubjectList = (ListView) findViewById(R.id.subject_list);
308         mSubjectList.setOnItemClickListener(mItemClickListener);
309         mSubjectList.setVisibility(View.GONE);
310 
311         updateContactInfo();
312         updateCharacterLimit();
313     }
314 
315     /**
316      * Populates the contact info fields based on the current contact information.
317      */
updateContactInfo()318     private void updateContactInfo() {
319         if (mContactUri != null) {
320             setPhoto(mPhotoID, mPhotoUri, mContactUri, mNameOrNumber, mIsBusiness);
321         } else {
322             mContactPhoto.setVisibility(View.GONE);
323         }
324         mNameView.setText(mNameOrNumber);
325         if (!TextUtils.isEmpty(mNumberLabel) && !TextUtils.isEmpty(mDisplayNumber)) {
326             mNumberView.setVisibility(View.VISIBLE);
327             mNumberView.setText(getString(R.string.call_subject_type_and_number,
328                     mNumberLabel, mDisplayNumber));
329         } else {
330             mNumberView.setVisibility(View.GONE);
331             mNumberView.setText(null);
332         }
333     }
334 
335     /**
336      * Reads arguments from the fragment arguments and populates the necessary instance variables.
337      */
readArguments()338     private void readArguments() {
339         Bundle arguments = getIntent().getExtras();
340         if (arguments == null) {
341             Log.e(TAG, "Arguments cannot be null.");
342             return;
343         }
344         mPhotoID = arguments.getLong(ARG_PHOTO_ID);
345         mPhotoUri = arguments.getParcelable(ARG_PHOTO_URI);
346         mContactUri = arguments.getParcelable(ARG_CONTACT_URI);
347         mNameOrNumber = arguments.getString(ARG_NAME_OR_NUMBER);
348         mIsBusiness = arguments.getBoolean(ARG_IS_BUSINESS);
349         mNumber = arguments.getString(ARG_NUMBER);
350         mDisplayNumber = arguments.getString(ARG_DISPLAY_NUMBER);
351         mNumberLabel = arguments.getString(ARG_NUMBER_LABEL);
352         mPhoneAccountHandle = arguments.getParcelable(ARG_PHONE_ACCOUNT_HANDLE);
353     }
354 
355     /**
356      * Updates the character limit display, coloring the text RED when the limit is reached or
357      * exceeded.
358      */
updateCharacterLimit()359     private void updateCharacterLimit() {
360         String subjectText = mCallSubjectView.getText().toString();
361         final int length;
362 
363         // If a message encoding is specified, use that to count bytes in the message.
364         if (mMessageEncoding != null) {
365             length = subjectText.getBytes(mMessageEncoding).length;
366         } else {
367             // No message encoding specified, so just count characters entered.
368             length = subjectText.length();
369         }
370 
371         mCharacterLimitView.setText(
372                 getString(R.string.call_subject_limit, length, mLimit));
373         if (length >= mLimit) {
374             mCharacterLimitView.setTextColor(getResources().getColor(
375                     R.color.call_subject_limit_exceeded));
376         } else {
377             mCharacterLimitView.setTextColor(getResources().getColor(
378                     R.color.dialtacts_secondary_text_color));
379         }
380     }
381 
382     /**
383      * Sets the photo on the quick contact photo.
384      *
385      * @param photoId
386      * @param photoUri
387      * @param contactUri
388      * @param displayName
389      * @param isBusiness
390      */
setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName, boolean isBusiness)391     private void setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName,
392             boolean isBusiness) {
393         mContactPhoto.assignContactUri(contactUri);
394         if (CompatUtils.isLollipopCompatible()) {
395             mContactPhoto.setOverlay(null);
396         }
397 
398         int contactType;
399         if (isBusiness) {
400             contactType = ContactPhotoManager.TYPE_BUSINESS;
401         } else {
402             contactType = ContactPhotoManager.TYPE_DEFAULT;
403         }
404 
405         String lookupKey = null;
406         if (contactUri != null) {
407             lookupKey = UriUtils.getLookupKeyFromUri(contactUri);
408         }
409 
410         ContactPhotoManager.DefaultImageRequest
411                 request = new ContactPhotoManager.DefaultImageRequest(
412                 displayName, lookupKey, contactType, true /* isCircular */);
413 
414         if (photoId == 0 && photoUri != null) {
415             ContactPhotoManager.getInstance(this).loadPhoto(mContactPhoto, photoUri,
416                     mPhotoSize, false /* darkTheme */, true /* isCircular */, request);
417         } else {
418             ContactPhotoManager.getInstance(this).loadThumbnail(mContactPhoto, photoId,
419                     false /* darkTheme */, true /* isCircular */, request);
420         }
421     }
422 
423     /**
424      * Loads the subject history from shared preferences.
425      *
426      * @param prefs Shared preferences.
427      * @return List of subject history strings.
428      */
loadSubjectHistory(SharedPreferences prefs)429     public static List<String> loadSubjectHistory(SharedPreferences prefs) {
430         int historySize = prefs.getInt(PREF_KEY_SUBJECT_HISTORY_COUNT, 0);
431         List<String> subjects = new ArrayList(historySize);
432 
433         for (int ix = 0 ; ix < historySize; ix++) {
434             String historyItem = prefs.getString(PREF_KEY_SUBJECT_HISTORY_ITEM + ix, null);
435             if (!TextUtils.isEmpty(historyItem)) {
436                 subjects.add(historyItem);
437             }
438         }
439 
440         return subjects;
441     }
442 
443     /**
444      * Saves the subject history list to shared prefs, removing older items so that there are only
445      * {@link #CALL_SUBJECT_HISTORY_SIZE} items at most.
446      *
447      * @param history The history.
448      */
saveSubjectHistory(List<String> history)449     private void saveSubjectHistory(List<String> history) {
450         // Remove oldest subject(s).
451         while (history.size() > CALL_SUBJECT_HISTORY_SIZE) {
452             history.remove(0);
453         }
454 
455         SharedPreferences.Editor editor = mPrefs.edit();
456         int historyCount = 0;
457         for (String subject : history) {
458             if (!TextUtils.isEmpty(subject)) {
459                 editor.putString(PREF_KEY_SUBJECT_HISTORY_ITEM + historyCount,
460                         subject);
461                 historyCount++;
462             }
463         }
464         editor.putInt(PREF_KEY_SUBJECT_HISTORY_COUNT, historyCount);
465         editor.apply();
466     }
467 
468     /**
469      * Hide software keyboard for the given {@link View}.
470      */
hideSoftKeyboard(Context context, View view)471     public void hideSoftKeyboard(Context context, View view) {
472         InputMethodManager imm = (InputMethodManager) context.getSystemService(
473                 Context.INPUT_METHOD_SERVICE);
474         if (imm != null) {
475             imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
476         }
477     }
478 
479     /**
480      * Hides or shows the call history list.
481      *
482      * @param show {@code true} if the call history should be shown, {@code false} otherwise.
483      */
showCallHistory(final boolean show)484     private void showCallHistory(final boolean show) {
485         // Bail early if the visibility has not changed.
486         if ((show && mSubjectList.getVisibility() == View.VISIBLE) ||
487                 (!show && mSubjectList.getVisibility() == View.GONE)) {
488             return;
489         }
490 
491         final int dialogStartingBottom = mDialogView.getBottom();
492         if (show) {
493             // Showing the subject list; bind the list of history items to the list and show it.
494             ArrayAdapter<String> adapter = new ArrayAdapter<String>(CallSubjectDialog.this,
495                     R.layout.call_subject_history_list_item, mSubjectHistory);
496             mSubjectList.setAdapter(adapter);
497             mSubjectList.setVisibility(View.VISIBLE);
498         } else {
499             // Hiding the subject list.
500             mSubjectList.setVisibility(View.GONE);
501         }
502 
503         // Use a ViewTreeObserver so that we can animate between the pre-layout and post-layout
504         // states.
505         final ViewTreeObserver observer = mBackgroundView.getViewTreeObserver();
506         observer.addOnPreDrawListener(
507                 new ViewTreeObserver.OnPreDrawListener() {
508                     @Override
509                     public boolean onPreDraw() {
510                         // We don't want to continue getting called.
511                         if (observer.isAlive()) {
512                             observer.removeOnPreDrawListener(this);
513                         }
514 
515                         // Determine the amount the dialog has shifted due to the relayout.
516                         int shiftAmount = dialogStartingBottom - mDialogView.getBottom();
517 
518                         // If the dialog needs to be shifted, do that now.
519                         if (shiftAmount != 0) {
520                             // Start animation in translated state and animate to translationY 0.
521                             mDialogView.setTranslationY(shiftAmount);
522                             mDialogView.animate()
523                                     .translationY(0)
524                                     .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
525                                     .setDuration(mAnimationDuration)
526                                     .start();
527                         }
528 
529                         if (show) {
530                             // Show the subhect list.
531                             mSubjectList.setTranslationY(mSubjectList.getHeight());
532 
533                             mSubjectList.animate()
534                                     .translationY(0)
535                                     .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
536                                     .setDuration(mAnimationDuration)
537                                     .setListener(new AnimatorListenerAdapter() {
538                                         @Override
539                                         public void onAnimationEnd(Animator animation) {
540                                             super.onAnimationEnd(animation);
541                                         }
542 
543                                         @Override
544                                         public void onAnimationStart(Animator animation) {
545                                             super.onAnimationStart(animation);
546                                             mSubjectList.setVisibility(View.VISIBLE);
547                                         }
548                                     })
549                                     .start();
550                         } else {
551                             // Hide the subject list.
552                             mSubjectList.setTranslationY(0);
553 
554                             mSubjectList.animate()
555                                     .translationY(mSubjectList.getHeight())
556                                     .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
557                                     .setDuration(mAnimationDuration)
558                                     .setListener(new AnimatorListenerAdapter() {
559                                         @Override
560                                         public void onAnimationEnd(Animator animation) {
561                                             super.onAnimationEnd(animation);
562                                             mSubjectList.setVisibility(View.GONE);
563                                         }
564 
565                                         @Override
566                                         public void onAnimationStart(Animator animation) {
567                                             super.onAnimationStart(animation);
568                                         }
569                                     })
570                                     .start();
571                         }
572                         return true;
573                     }
574                 }
575         );
576     }
577 
578     /**
579      * Loads the message encoding and maximum message length from the phone account extras for the
580      * current phone account.
581      */
loadConfiguration()582     private void loadConfiguration() {
583         // Only attempt to load configuration from the phone account extras if the SDK is N or
584         // later.  If we've got a prior SDK the default encoding and message length will suffice.
585         int sdk = android.os.Build.VERSION.SDK_INT;
586         if(sdk <= android.os.Build.VERSION_CODES.M) {
587             return;
588         }
589 
590         if (mPhoneAccountHandle == null) {
591             return;
592         }
593 
594         TelecomManager telecomManager =
595                 (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
596         final PhoneAccount account = telecomManager.getPhoneAccount(mPhoneAccountHandle);
597 
598         Bundle phoneAccountExtras = PhoneAccountSdkCompat.getExtras(account);
599         if (phoneAccountExtras == null) {
600             return;
601         }
602 
603         // Get limit, if provided; otherwise default to existing value.
604         mLimit = phoneAccountExtras
605                 .getInt(PhoneAccountSdkCompat.EXTRA_CALL_SUBJECT_MAX_LENGTH, mLimit);
606 
607         // Get charset; default to none (e.g. count characters 1:1).
608         String charsetName = phoneAccountExtras.getString(
609                 PhoneAccountSdkCompat.EXTRA_CALL_SUBJECT_CHARACTER_ENCODING);
610 
611         if (!TextUtils.isEmpty(charsetName)) {
612             try {
613                 mMessageEncoding = Charset.forName(charsetName);
614             } catch (java.nio.charset.UnsupportedCharsetException uce) {
615                 // Character set was invalid; log warning and fallback to none.
616                 Log.w(TAG, "Invalid charset: " + charsetName);
617                 mMessageEncoding = null;
618             }
619         } else {
620             // No character set specified, so count characters 1:1.
621             mMessageEncoding = null;
622         }
623     }
624 }
625