/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mms.ui;
import com.android.mms.MmsApp;
import com.android.mms.MmsConfig;
import com.android.mms.R;
import com.android.mms.LogTag;
import com.android.mms.data.WorkingMessage;
import com.android.mms.model.MediaModel;
import com.android.mms.model.SlideModel;
import com.android.mms.model.SlideshowModel;
import com.android.mms.transaction.MmsMessageSender;
import com.android.mms.util.AddressUtils;
import com.google.android.mms.ContentType;
import com.google.android.mms.MmsException;
import com.google.android.mms.pdu.CharacterSets;
import com.google.android.mms.pdu.EncodedStringValue;
import com.google.android.mms.pdu.MultimediaMessagePdu;
import com.google.android.mms.pdu.NotificationInd;
import com.google.android.mms.pdu.PduBody;
import com.google.android.mms.pdu.PduHeaders;
import com.google.android.mms.pdu.PduPart;
import com.google.android.mms.pdu.PduPersister;
import com.google.android.mms.pdu.RetrieveConf;
import com.google.android.mms.pdu.SendReq;
import android.database.sqlite.SqliteWrapper;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.StatFs;
import android.provider.Telephony.Mms;
import android.provider.MediaStore;
import android.media.CamcorderProfile;
import android.provider.Telephony.Sms;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.text.style.URLSpan;
import android.util.Log;
import android.widget.Toast;
import android.view.KeyEvent;
import android.webkit.MimeTypeMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import android.media.ExifInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import java.io.OutputStream;
/**
* An utility class for managing messages.
*/
public class MessageUtils {
static final int UNCONSTRAINED = -1;
static final int MAX_SIZE = 1000*1000;
interface ResizeImageResultCallback {
void onResizeResult(PduPart part, boolean append);
}
private static final String TAG = LogTag.TAG;
private static String sLocalNumber;
private static boolean needLocalNumber = true;
static AlertDialog.Builder builder = null;
// Cache of both groups of space-separated ids to their full
// comma-separated display names, as well as individual ids to
// display names.
// TODO: is it possible for canonical address ID keys to be
// re-used? SQLite does reuse IDs on NULL id_ insert, but does
// anything ever delete from the mmssms.db canonical_addresses
// table? Nothing that I could find.
private static final Map<String, String> sRecipientAddress =
new ConcurrentHashMap<String, String>(20 /* initial capacity */);
/**
* MMS address parsing data structures
*/
// allowable phone number separators
private static final char[] NUMERIC_CHARS_SUGAR = {
'-', '.', ',', '(', ')', ' ', '/', '\\', '*', '#', '+'
};
private static HashMap numericSugarMap = new HashMap (NUMERIC_CHARS_SUGAR.length);
static {
for (int i = 0; i < NUMERIC_CHARS_SUGAR.length; i++) {
numericSugarMap.put(NUMERIC_CHARS_SUGAR[i], NUMERIC_CHARS_SUGAR[i]);
}
}
private MessageUtils() {
// Forbidden being instantiated.
}
public static String getMessageDetails(Context context, Cursor cursor, int size) {
if (cursor == null) {
return null;
}
if ("mms".equals(cursor.getString(MessageListAdapter.COLUMN_MSG_TYPE))) {
int type = cursor.getInt(MessageListAdapter.COLUMN_MMS_MESSAGE_TYPE);
switch (type) {
case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
return getNotificationIndDetails(context, cursor);
case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
case PduHeaders.MESSAGE_TYPE_SEND_REQ:
return getMultimediaMessageDetails(context, cursor, size);
default:
Log.w(TAG, "No details could be retrieved.");
return "";
}
} else {
return getTextMessageDetails(context, cursor);
}
}
private static String getNotificationIndDetails(Context context, Cursor cursor) {
StringBuilder details = new StringBuilder();
Resources res = context.getResources();
long id = cursor.getLong(MessageListAdapter.COLUMN_ID);
Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, id);
NotificationInd nInd;
try {
nInd = (NotificationInd) PduPersister.getPduPersister(
context).load(uri);
} catch (MmsException e) {
Log.e(TAG, "Failed to load the message: " + uri, e);
return context.getResources().getString(R.string.cannot_get_details);
}
// Message Type: Mms Notification.
details.append(res.getString(R.string.message_type_label));
details.append(res.getString(R.string.multimedia_notification));
String service_center = cursor.getString(MessageListAdapter.COLUMN_MMS_SERVICE_CENTER);
Log.d(TAG,"getNotificationIndDetails() service_center:"+service_center);
details.append('\n');
details.append(res.getString(R.string.service_center));
details.append(!TextUtils.isEmpty(service_center)? service_center:"");
// From: ***
String from = extractEncStr(context, nInd.getFrom());
details.append('\n');
details.append(res.getString(R.string.from_label));
details.append(!TextUtils.isEmpty(from)? from:
res.getString(R.string.hidden_sender_address));
// Date: ***
details.append('\n');
details.append(res.getString(
R.string.expire_on,
MessageUtils.formatTimeStampString(
context, nInd.getExpiry() * 1000L, true)));
// Subject: ***
details.append('\n');
details.append(res.getString(R.string.subject_label));
EncodedStringValue subject = nInd.getSubject();
if (subject != null) {
details.append(subject.getString());
}
// Message class: Personal/Advertisement/Infomational/Auto
details.append('\n');
details.append(res.getString(R.string.message_class_label));
details.append(new String(nInd.getMessageClass()));
// Message size: *** KB
details.append('\n');
details.append(res.getString(R.string.message_size_label));
details.append(String.valueOf((nInd.getMessageSize() + 1023) / 1024));
details.append(context.getString(R.string.kilobyte));
return details.toString();
}
public static String getMultimediaMessageDetails(
Context context, Cursor cursor, int size) {
int type = cursor.getInt(MessageListAdapter.COLUMN_MMS_MESSAGE_TYPE);
if (type == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
return getNotificationIndDetails(context, cursor);
}
StringBuilder details = new StringBuilder();
Resources res = context.getResources();
long id = cursor.getLong(MessageListAdapter.COLUMN_ID);
Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, id);
MultimediaMessagePdu msg;
try {
msg = (MultimediaMessagePdu) PduPersister.getPduPersister(
context).load(uri);
} catch (MmsException e) {
Log.e(TAG, "Failed to load the message: " + uri, e);
return context.getResources().getString(R.string.cannot_get_details);
}
// Message Type: Text message.
details.append(res.getString(R.string.message_type_label));
details.append(res.getString(R.string.multimedia_message));
if (msg instanceof RetrieveConf) {
// From: ***
String from = extractEncStr(context, ((RetrieveConf) msg).getFrom());
details.append('\n');
details.append(res.getString(R.string.from_label));
details.append(!TextUtils.isEmpty(from)? from:
res.getString(R.string.hidden_sender_address));
}
// To: ***
details.append('\n');
details.append(res.getString(R.string.to_address_label));
EncodedStringValue[] to = msg.getTo();
if (to != null) {
details.append(EncodedStringValue.concat(to));
}
else {
Log.w(TAG, "recipient list is empty!");
}
// Bcc: ***
if (msg instanceof SendReq) {
EncodedStringValue[] values = ((SendReq) msg).getBcc();
if ((values != null) && (values.length > 0)) {
details.append('\n');
details.append(res.getString(R.string.bcc_label));
details.append(EncodedStringValue.concat(values));
}
}
// Date: ***
details.append('\n');
int msgBox = cursor.getInt(MessageListAdapter.COLUMN_MMS_MESSAGE_BOX);
if (msgBox == Mms.MESSAGE_BOX_DRAFTS) {
details.append(res.getString(R.string.saved_label));
} else if (msgBox == Mms.MESSAGE_BOX_INBOX) {
details.append(res.getString(R.string.received_label));
} else {
details.append(res.getString(R.string.sent_label));
}
details.append(MessageUtils.formatTimeStampString(
context, msg.getDate() * 1000L, true));
// Subject: ***
details.append('\n');
details.append(res.getString(R.string.subject_label));
EncodedStringValue subject = msg.getSubject();
if (subject != null) {
String subStr = subject.getString();
// Message size should include size of subject.
size += subStr.getBytes().length;
details.append(subStr);
}
// ======fixed CR<NEWMS00110179> by luning at 11-08-12 begin======
else{
details.append(context.getResources().getString(R.string.no_subject_view));
}
// ======fixed CR<NEWMS00110179> by luning at 11-08-12 end======
// Priority: High/Normal/Low
details.append('\n');
details.append(res.getString(R.string.priority_label));
details.append(getPriorityDescription(context, msg.getPriority()));
// Message size: *** KB
details.append('\n');
details.append(res.getString(R.string.message_size_label));
details.append(MmsConfig.getMessageWithPduHeadSize(size)/1024);
details.append(" KB");
return details.toString();
}
private static String getTextMessageDetails(Context context, Cursor cursor) {
StringBuilder details = new StringBuilder();
Resources res = context.getResources();
// Message Type: Text message.
details.append(res.getString(R.string.message_type_label));
details.append(res.getString(R.string.text_message));
// Address: ***
details.append('\n');
int smsType = cursor.getInt(MessageListAdapter.COLUMN_SMS_TYPE);
if (Sms.isOutgoingFolder(smsType)) {
details.append(res.getString(R.string.to_address_label));
} else {
details.append(res.getString(R.string.from_label));
}
details.append(cursor.getString(MessageListAdapter.COLUMN_SMS_ADDRESS));
// Date: ***
details.append('\n');
if (smsType == Sms.MESSAGE_TYPE_DRAFT) {
details.append(res.getString(R.string.saved_label));
} else if (smsType == Sms.MESSAGE_TYPE_INBOX) {
details.append(res.getString(R.string.received_label));
} else {
details.append(res.getString(R.string.sent_label));
}
long date = cursor.getLong(MessageListAdapter.COLUMN_SMS_DATE);
details.append(MessageUtils.formatTimeStampString(context, date, true));
// Error code: ***
int errorCode = cursor.getInt(MessageListAdapter.COLUMN_SMS_ERROR_CODE);
if (errorCode != 0) {
details.append('\n')
.append(res.getString(R.string.error_code_label))
.append(errorCode);
}
return details.toString();
}
static private String getPriorityDescription(Context context, int PriorityValue) {
Resources res = context.getResources();
switch(PriorityValue) {
case PduHeaders.PRIORITY_HIGH:
return res.getString(R.string.priority_high);
case PduHeaders.PRIORITY_LOW:
return res.getString(R.string.priority_low);
case PduHeaders.PRIORITY_NORMAL:
default:
return res.getString(R.string.priority_normal);
}
}
public static int getAttachmentType(SlideshowModel model) {
if (model == null) {
return WorkingMessage.TEXT;
}
int numberOfSlides = model.size();
if (numberOfSlides > 1) {
return WorkingMessage.SLIDESHOW;
} else if (numberOfSlides == 1) {
// Only one slide in the slide-show.
SlideModel slide = model.get(0);
if (slide.hasVideo()) {
return WorkingMessage.VIDEO;
}
if (slide.hasAudio() && slide.hasImage()) {
return WorkingMessage.SLIDESHOW;
}
if (slide.hasAudio()) {
return WorkingMessage.AUDIO;
}
if (slide.hasImage()) {
return WorkingMessage.IMAGE;
}
if (slide.hasVcard()) {
return WorkingMessage.VCARD;
}
if (slide.hasOtherFile()) {
return WorkingMessage.OTHER_FILE;
}
// if (slide.hasText()) {
// return WorkingMessage.TEXT;
// }
}
//===== fixed CR<NEWMS00137442> by luning at 11-10-26 begin =====
if(model.hasVcard()){
return WorkingMessage.VCARD;
}
//===== fixed CR<NEWMS00137442> by luning at 11-10-26 end =====
if(model.hasOtherFile()){/*fixed CR<NEWMS00144166> by luning at 2011.11.28*/
return WorkingMessage.OTHER_FILE;
}
return WorkingMessage.TEXT;
}
public static String formatTimeStampString(Context context, long when) {
return formatTimeStampString(context, when, false);
}
public static String formatTimeStampString(Context context, long when, boolean fullFormat) {
Time then = new Time();
then.set(when);
Time now = new Time();
now.setToNow();
// Basic settings for formatDateTime() we want for all cases.
int format_flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT |
DateUtils.FORMAT_ABBREV_ALL |
DateUtils.FORMAT_CAP_AMPM;
// If the message is from a different year, show the date and year.
if (then.year != now.year) {
format_flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
} else if (then.yearDay != now.yearDay) {
// If it is from a different day than today, show only the date.
format_flags |= DateUtils.FORMAT_SHOW_DATE;
} else {
// Otherwise, if the message is from today, show the time.
format_flags |= DateUtils.FORMAT_SHOW_TIME;
}
// If the caller has asked for full details, make sure to show the date
// and time no matter what we've determined above (but still make showing
// the year only happen if it is a different year from today).
if (fullFormat) {
format_flags |= (DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
return DateUtils.formatDateTime(context, when, format_flags);
}
/**
* @parameter recipientIds space-separated list of ids
*/
public static String getRecipientsByIds(Context context, String recipientIds,
boolean allowQuery) {
String value = sRecipientAddress.get(recipientIds);
if (value != null) {
return value;
}
if (!TextUtils.isEmpty(recipientIds)) {
StringBuilder addressBuf = extractIdsToAddresses(
context, recipientIds, allowQuery);
if (addressBuf == null) {
// temporary error? Don't memoize.
return "";
}
value = addressBuf.toString();
} else {
value = "";
}
sRecipientAddress.put(recipientIds, value);
return value;
}
private static StringBuilder extractIdsToAddresses(Context context, String recipients,
boolean allowQuery) {
StringBuilder addressBuf = new StringBuilder();
String[] recipientIds = recipients.split(" ");
boolean firstItem = true;
for (String recipientId : recipientIds) {
String value = sRecipientAddress.get(recipientId);
if (value == null) {
if (!allowQuery) {
// when allowQuery is false, if any value from sRecipientAddress.get() is null,
// return null for the whole thing. We don't want to stick partial result
// into sRecipientAddress for multiple recipient ids.
return null;
}
Uri uri = Uri.parse("content://mms-sms/canonical-address/" + recipientId);
Cursor c = SqliteWrapper.query(context, context.getContentResolver(),
uri, null, null, null, null);
if (c != null) {
try {
if (c.moveToFirst()) {
value = c.getString(0);
sRecipientAddress.put(recipientId, value);
}
} finally {
c.close();
}
}
}
if (value == null) {
continue;
}
if (firstItem) {
firstItem = false;
} else {
addressBuf.append(";");
}
addressBuf.append(value);
}
return (addressBuf.length() == 0) ? null : addressBuf;
}
public static void selectAudio(Context context, int requestCode) {
if (context instanceof Activity) {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_INCLUDE_DRM, false);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE,
context.getString(R.string.select_audio));
((Activity) context).startActivityForResult(intent, requestCode);
}
}
public static void recordSound(Context context, int requestCode) {
if (context instanceof Activity) {
long sizeLimit = MmsConfig.getMaxMessageSize() - SlideshowModel.SLIDESHOW_SLOP;
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType(ContentType.AUDIO_AMR);
intent.putExtra("android.intent.extra.sizeLimit", sizeLimit);
intent.setClassName("com.android.soundrecorder",
"com.android.soundrecorder.SoundRecorder");
((Activity) context).startActivityForResult(intent, requestCode);
}
}
public static void recordVideo(Context context, int requestCode, long sizeLimit) {
if (context instanceof Activity) {
if (sizeLimit > 0) {
int durationLimit = CamcorderProfile
.get(CamcorderProfile.QUALITY_LOW).duration;
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
intent.putExtra("android.intent.extra.sizeLimit", sizeLimit);
intent.putExtra("android.intent.extra.durationLimit",
durationLimit);
((Activity) context)
.startActivityForResult(intent, requestCode);
}
}
}
public static void selectVideo(Context context, int requestCode) {
selectMediaByType(context, requestCode, ContentType.VIDEO_UNSPECIFIED);
}
public static void selectImage(Context context, int requestCode) {
selectMediaByType(context, requestCode, ContentType.IMAGE_UNSPECIFIED);
}
private static void selectMediaByType(
Context context, int requestCode, String contentType) {
if (context instanceof Activity) {
Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT);
innerIntent.setType(contentType);
Intent wrapperIntent = Intent.createChooser(innerIntent, null);
((Activity) context).startActivityForResult(wrapperIntent, requestCode);
}
}
public static void viewSimpleSlideshow(Context context, SlideshowModel slideshow) {
if (!slideshow.isSimple()) {
throw new IllegalArgumentException(
"viewSimpleSlideshow() called on a non-simple slideshow");
}
SlideModel slide = slideshow.get(0);
MediaModel mm = null;
if (slide.hasImage()) {
mm = slide.getImage();
} else if (slide.hasVideo()) {
mm = slide.getVideo();
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//CR NEWMS00107171 Modify Start
intent.putExtra("isFromMms", true);
//CR NEWMS00107171 Modify End
String contentType;
if (mm.isDrmProtected()) {
contentType = mm.getDrmObject().getContentType();
} else {
contentType = mm.getContentType();
}
intent.setDataAndType(mm.getUri(), contentType);
context.startActivity(intent);
}
public static void showErrorDialog(Context context,
String title, String message) {
if(builder!=null){
return;
}
builder = new AlertDialog.Builder(context);
String messagebody = message;
builder.setIcon(R.drawable.ic_sms_mms_not_delivered);
builder.setTitle(title);
if(title.equals(context.getString(R.string.exceed_message_size_limitation))){
messagebody = messagebody + "\n" + context.getString(R.string.size_limit_comments_all);
}
builder.setMessage(messagebody);
builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
dialog.dismiss();
builder = null;
}
}
});
builder.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_BACK) {
dialog.dismiss();
builder = null;
}
return false;
}
});
builder.show();
}
/**
* The quality parameter which is used to compress JPEG images.
*/
public static final int IMAGE_COMPRESSION_QUALITY = 80;
/**
* The minimum quality parameter which is used to compress JPEG images.
*/
public static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50;
public static Uri saveBitmapAsPart(Context context, Uri messageUri, Bitmap bitmap)
throws MmsException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.JPEG, IMAGE_COMPRESSION_QUALITY, os);
PduPart part = new PduPart();
part.setContentType("image/jpeg".getBytes());
String contentId = "Image" + System.currentTimeMillis();
part.setContentLocation((contentId + ".jpg").getBytes());
part.setContentId(contentId.getBytes());
part.setData(os.toByteArray());
Uri retVal = PduPersister.getPduPersister(context).persistPart(part,
ContentUris.parseId(messageUri));
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
log("saveBitmapAsPart: persisted part with uri=" + retVal);
}
return retVal;
}
/**
* Message overhead that reduces the maximum image byte size.
* 5000 is a realistic overhead number that allows for user to also include
* a small MIDI file or a couple pages of text along with the picture.
*/
public static final int MESSAGE_OVERHEAD = 5000;
public static void resizeImageAsync(final Context context,
final Uri imageUri, final Handler handler,
final ResizeImageResultCallback cb,
final boolean append) {
// Show a progress toast if the resize hasn't finished
// within one second.
// Stash the runnable for showing it away so we can cancel
// it later if the resize completes ahead of the deadline.
final Runnable showProgress = new Runnable() {
public void run() {
Toast.makeText(context, R.string.compressing, Toast.LENGTH_SHORT).show();
}
};
//===== fixed CR<NEWSM00125959> by luning at 11-09-26 begin =====
// Schedule it for one second from now.
// handler.postDelayed(showProgress, 1000);
// Schedule it at once!
handler.post(showProgress);
//===== fixed CR<NEWSM00125959> by luning at 11-09-26 end =====
new Thread(new Runnable() {
public void run() {
final PduPart part;
try {
UriImage image = new UriImage(context, imageUri);
part = image.getResizedImageAsPart(
MmsConfig.getMaxImageWidth(),
MmsConfig.getMaxImageHeight(),
MmsConfig.getMaxMessageSize() - MESSAGE_OVERHEAD);
} finally {
// Cancel pending show of the progress toast if necessary.
handler.removeCallbacks(showProgress);
}
handler.post(new Runnable() {
public void run() {
cb.onResizeResult(part, append);
}
});
}
}).start();
}
public static void showDiscardDraftConfirmDialog(Context context,
OnClickListener listener) {
new AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.discard_message)
.setMessage(R.string.discard_message_reason)
.setPositiveButton(R.string.yes, listener)
//.setNeutralButton(R.string.save_as_draft, listener) //add by liguxiang 10-11-11 for NEWM00129822
.setNegativeButton(R.string.no, null)
.show();
}
public static String getLocalNumber() {
//if (null == sLocalNumber) {
if ( needLocalNumber ) {
sLocalNumber = MmsApp.getApplication().getTelephonyManager().getLine1Number();
needLocalNumber = false;
}
return sLocalNumber;
}
public static boolean isLocalNumber(String number) {
if (number == null) {
return false;
}
// we don't use Mms.isEmailAddress() because it is too strict for comparing addresses like
// "foo+caf_=6505551212=tmomail.net@gmail.com", which is the 'from' address from a forwarded email
// message from Gmail. We don't want to treat "foo+caf_=6505551212=tmomail.net@gmail.com" and
// "6505551212" to be the same.
if (number.indexOf('@') >= 0) {
return false;
}
return PhoneNumberUtils.compare(number, getLocalNumber());
}
public static void handleReadReport(final Context context,
final long threadId,
final int status,
final Runnable callback) {
String selection = Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF
+ " AND " + Mms.READ + " = 0"
+ " AND " + Mms.READ_REPORT + " = " + PduHeaders.VALUE_YES;
if (threadId != -1) {
selection = selection + " AND " + Mms.THREAD_ID + " = " + threadId;
}
final Cursor c = SqliteWrapper.query(context, context.getContentResolver(),
Mms.Inbox.CONTENT_URI, new String[] {Mms._ID, Mms.MESSAGE_ID},
selection, null, null);
if (c == null) {
return;
}
final Map<String, String> map = new HashMap<String, String>();
try {
if (c.getCount() == 0) {
if (callback != null) {
callback.run();
}
return;
}
while (c.moveToNext()) {
Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, c.getLong(0));
map.put(c.getString(1), AddressUtils.getFrom(context, uri));
}
} finally {
c.close();
}
OnClickListener positiveListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
for (final Map.Entry<String, String> entry : map.entrySet()) {
MmsMessageSender.sendReadRec(context, entry.getValue(),
entry.getKey(), status);
}
if (callback != null) {
callback.run();
}
dialog.dismiss();
}
};
OnClickListener negativeListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (callback != null) {
callback.run();
}
dialog.dismiss();
}
};
OnCancelListener cancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
if (callback != null) {
callback.run();
}
dialog.dismiss();
}
};
confirmReadReportDialog(context, positiveListener,
negativeListener,
cancelListener);
}
//===== fixed CR<NEWMS00127040> by luning at 11-10-07 begin =====
public static void handleReadReport(final Context context,
final HashMap<String, String> map, final int status,
final Runnable callback) {
OnClickListener positiveListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
for (final Map.Entry<String, String> entry : map.entrySet()) {
MmsMessageSender.sendReadRec(context, entry.getValue(),
entry.getKey(), status);
}
if (callback != null) {
callback.run();
}
}
};
OnClickListener negativeListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (callback != null) {
callback.run();
}
}
};
OnCancelListener cancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
if (callback != null) {
callback.run();
}
}
};
confirmReadReportDialog(context, positiveListener, negativeListener,
cancelListener);
}
//===== fixed CR<NEWMS00127040> by luning at 11-10-07 end =====
private static void confirmReadReportDialog(Context context,
OnClickListener positiveListener, OnClickListener negativeListener,
OnCancelListener cancelListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setCancelable(true);
builder.setTitle(R.string.confirm);
builder.setMessage(R.string.message_send_read_report);
builder.setPositiveButton(R.string.yes, positiveListener);
builder.setNegativeButton(R.string.no, negativeListener);
builder.setOnCancelListener(cancelListener);
builder.show();
}
public static String extractEncStrFromCursor(Cursor cursor,
int columnRawBytes, int columnCharset) {
String rawBytes = cursor.getString(columnRawBytes);
int charset = cursor.getInt(columnCharset);
if (TextUtils.isEmpty(rawBytes)) {
return "";
} else if (charset == CharacterSets.ANY_CHARSET) {
return rawBytes;
} else {
return new EncodedStringValue(charset, PduPersister.getBytes(rawBytes)).getString();
}
}
private static String extractEncStr(Context context, EncodedStringValue value) {
if (value != null) {
return value.getString();
} else {
return "";
}
}
public static ArrayList<String> extractUris(URLSpan[] spans) {
int size = spans.length;
ArrayList<String> accumulator = new ArrayList<String>();
for (int i = 0; i < size; i++) {
accumulator.add(spans[i].getURL());
}
return accumulator;
}
/**
* Play/view the message attachments.
* TOOD: We need to save the draft before launching another activity to view the attachments.
* This is hacky though since we will do saveDraft twice and slow down the UI.
* We should pass the slideshow in intent extra to the view activity instead of
* asking it to read attachments from database.
* @param context
* @param msgUri the MMS message URI in database
* @param slideshow the slideshow to save
* @param persister the PDU persister for updating the database
* @param sendReq the SendReq for updating the database
*/
public static void viewMmsMessageAttachment(Context context, Uri msgUri,
SlideshowModel slideshow) {
boolean isSimple = (slideshow == null) ? false : slideshow.isSimple();
// if (isSimple) {
// // In attachment-editor mode, we only ever have one slide.
// MessageUtils.viewSimpleSlideshow(context, slideshow);
// } else {
// // If a slideshow was provided, save it to disk first.
if (slideshow != null) {
PduPersister persister = PduPersister.getPduPersister(context);
try {
PduBody pb = slideshow.toPduBody();
persister.updateParts(msgUri, pb);
slideshow.sync(pb);
} catch (MmsException e) {
Log.e(TAG, "Unable to save message for preview");
return;
}
}
// Launch the slideshow activity to play/view.
Intent intent = new Intent(context, SlideshowActivity.class);
intent.setData(msgUri);
context.startActivity(intent);
// }
}
public static void viewMmsMessageAttachment(Context context, WorkingMessage msg) {
if (msg.isDiscarded()) {
return;
}
SlideshowModel slideshow = msg.getSlideshow();
if (slideshow == null) {
throw new IllegalStateException("msg.getSlideshow() == null");
}
if (slideshow.isSimple()) {
MessageUtils.viewSimpleSlideshow(context, slideshow);
} else {
Uri uri = msg.saveAsMms(false);
viewMmsMessageAttachment(context, uri, slideshow);
}
}
/**
* Debugging
*/
public static void writeHprofDataToFile(){
String filename = Environment.getExternalStorageDirectory() + "/mms_oom_hprof_data";
try {
android.os.Debug.dumpHprofData(filename);
Log.i(TAG, "written hprof data to " + filename);
} catch (IOException ex) {
Log.e(TAG, "writeHprofDataToFile: caught IOException --> " + ex);
}
// ===== fixed CR<NEWMS00121388> by luning at 11-09-15 begin=====
catch (Exception ex){
Log.e(TAG, "writeHprofDataToFile: caught Exception --> " + ex);
}
// ===== fixed CR<NEWMS00121388> by luning at 11-09-15 end=====
}
public static boolean isAlias(String string) {
if (!MmsConfig.isAliasEnabled()) {
return false;
}
if (TextUtils.isEmpty(string)) {
return false;
}
// TODO: not sure if this is the right thing to use. Mms.isPhoneNumber() is
// intended for searching for things that look like they might be phone numbers
// in arbitrary text, not for validating whether something is in fact a phone number.
// It will miss many things that are legitimate phone numbers.
if (Mms.isPhoneNumber(string)) {
return false;
}
if (!isAlphaNumeric(string)) {
return false;
}
int len = string.length();
if (len < MmsConfig.getAliasMinChars() || len > MmsConfig.getAliasMaxChars()) {
return false;
}
return true;
}
public static boolean isAlphaNumeric(String s) {
char[] chars = s.toCharArray();
for (int x = 0; x < chars.length; x++) {
char c = chars[x];
if ((c >= 'a') && (c <= 'z')) {
continue;
}
if ((c >= 'A') && (c <= 'Z')) {
continue;
}
if ((c >= '0') && (c <= '9')) {
continue;
}
return false;
}
return true;
}
/**
* Given a phone number, return the string without syntactic sugar, meaning parens,
* spaces, slashes, dots, dashes, etc. If the input string contains non-numeric
* non-punctuation characters, return null.
*/
private static String parsePhoneNumberForMms(String address) {
StringBuilder builder = new StringBuilder();
int len = address.length();
for (int i = 0; i < len; i++) {
char c = address.charAt(i);
// accept the first '+' in the address
if (c == '+' && builder.length() == 0) {
builder.append(c);
continue;
}
if (Character.isDigit(c)) {
builder.append(c);
continue;
}
if (numericSugarMap.get(c) == null) {
return null;
}
}
return builder.toString();
}
/**
* Returns true if the address passed in is a valid MMS address.
*/
public static boolean isValidMmsAddress(String address) {
String retVal = parseMmsAddress(address);
Log.d(TAG, "[isValidMmsAddress] address =" + address);
return (retVal != null && !retVal.trim().equals(""));
}
/**
* parse the input address to be a valid MMS address.
* - if the address is an email address, leave it as is.
* - if the address can be parsed into a valid MMS phone number, return the parsed number.
* - if the address is a compliant alias address, leave it as is.
*/
public static String parseMmsAddress(String address) {
// if it's a valid Email address, use that.
if (Mms.isEmailAddress(address)) {
return address;
}
// if we are able to parse the address to a MMS compliant phone number, take that.
String retVal = parsePhoneNumberForMms(address);
if (retVal != null) {
return retVal;
}
// if it's an alias compliant address, use that.
if (isAlias(address)) {
return address;
}
// it's not a valid MMS address, return null
return null;
}
public static boolean isSimMemFull(int phoneId) {
SmsManager smsManager = SmsManager.getDefault(phoneId);
String capaStr = smsManager.getSimCapacity();
Log.d(TAG, "[sms]isSimMemFull =" + capaStr);
if (capaStr != null) {
String[] splitStr = capaStr.split(":");
Log.d(TAG, "[sms]isSimMemFull simUsed:" + splitStr[0]);
Log.d(TAG, "[sms]isSimMemFull simTotal:" + splitStr[1]);
return splitStr[0].equals(splitStr[1]);
} else {
Log.d(TAG, "[sms]isSimMemFull get capaStr fail");
return false;
}
}
private static void log(String msg) {
Log.d(TAG, "[MsgUtils] " + msg);
}
// lino add begin
public static final int NO_SDCARD = -1;
public static final int SDCARD_READ_ONLY = 0;
public static final int SDCARD_BUSY = 1;
public static final int SDCARD_WRITE = 2;
public static final int SDCARD_NO_SIZE = 3;
public static final int SDCARD_AVAILABLE = 4;
public static final String SAVE_MMS_DIR = "mms/";
public static boolean showSaveErroDialog(Context context, String filename, long filesize) {
int sdcardstatus = getSdcardStatus();
int error;
if (sdcardstatus == SDCARD_WRITE) {
long sdsize = getSdcardAvailableSize();
Log.d(TAG,"showSaveErroDialog() filesize:"+filesize+" sdsize:"+sdsize+" (filesize < sdsize):"+(filesize < sdsize));
if (filesize < sdsize) {
error = SDCARD_AVAILABLE;
} else {
error = SDCARD_NO_SIZE;
}
} else {
error = sdcardstatus;
}
if (error == SDCARD_AVAILABLE) {
return true;
}
int title = 0;
String msg = "";
switch (error) {
case NO_SDCARD:
title = R.string.save_no_sdcard_dlg_title;
msg = context.getString(R.string.save_no_sdcard_dlg_msg, filename);
break;
case SDCARD_READ_ONLY:
title = R.string.save_file_error_read_only_title;
msg = context.getString(R.string.save_file_error_read_only_msg);
break;
case SDCARD_BUSY:
title = R.string.save_sdcard_busy_dlg_title;
msg = context.getString(R.string.save_sdcard_busy_dlg_msg);
break;
case SDCARD_NO_SIZE:
title = R.string.save_file_error_dlg_title;
msg = context.getString(R.string.save_file_error_dlg_msg, filename);
break;
}
new AlertDialog.Builder(context).setTitle(title)
.setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg).setPositiveButton(
R.string.yes, null).show();
return false;
}
// Check to see if we have an SDCard and Sdcard can been written
public static int getSdcardStatus() {
String status = Environment.getExternalStorageState();
if (!status.equals(Environment.MEDIA_MOUNTED)) {
return NO_SDCARD;
}
if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
return SDCARD_READ_ONLY;
}
if (status.equals(Environment.MEDIA_SHARED)) {
return SDCARD_BUSY;
}
return SDCARD_WRITE;
}
// get the available size of sdcard
public static long getSdcardAvailableSize() {
StatFs statfs = new StatFs(Environment.getExternalStorageDirectory().getPath());
long size = (long)statfs.getBlockSize() * (long)statfs.getAvailableBlocks();
return size;
}
public static boolean saveFile(Context context, MediaModel mediaModel, String dir) {
File sdcard = Environment.getExternalStorageDirectory();
File fdir = new File(sdcard, dir);
if (!fdir.isDirectory() || !fdir.exists()) {
fdir.mkdir();
}
String filename = "";//fix for bug 11792
if (mediaModel.getSrc().startsWith("cid:")) {
filename = mediaModel.getSrc().substring("cid:".length());
}else{
filename = mediaModel.getSrc();
}
// add for some media donot have suffix
if (filename.indexOf(".") == -1) {
String type = new String(mediaModel.getContentType());
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
filename = filename + "." + extension;
}
File file = new File(fdir, filename);
FileOutputStream fops;
InputStream ips;
try {
ips = context.getContentResolver().openInputStream(mediaModel.getUri());
fops = new FileOutputStream(file);
byte[] data = new byte[1024];
try {
while (ips.read(data) != -1) {
fops.write(data);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} finally {
if (ips != null) {
try {
ips.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (fops != null) {
try {
fops.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
// Notify other applications listening to scanner events
// that a media file has been added to the sd card
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.fromFile(file)));
return true;
}
// lino add end
//Add by huibin For Image Rotate
public static Bitmap makeBitmap(String str) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(str,options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
return null;
}
options.inSampleSize = computeSampleSize(
options, UNCONSTRAINED, MmsConfig.getMaxImageHeight() * MmsConfig.getMaxImageWidth());
Log.i(TAG, "MmsConfig.getMaxImageHeight() is"+MmsConfig.getMaxImageHeight());
Log.i(TAG, "MmsConfig.getMaxImageWidth() is"+MmsConfig.getMaxImageWidth());
options.inJustDecodeBounds = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeFile(str,options);
} catch (OutOfMemoryError ex) {
Log.e(TAG, "Got oom exception ", ex);
return null;
}
}
public static int getExifOrientation(String filepath) {
int degree = 0;
ExifInterface exif = null;
try {
exif = new ExifInterface(filepath);
} catch (IOException ex) {
Log.e(TAG, "cannot read exif", ex);
}
if (exif != null) {
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION, -1);
if (orientation != -1) {
// We only recognize a subset of orientation tag values.
switch(orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
}
}
return degree;
}
public static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength,
maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == UNCONSTRAINED) &&
(minSideLength == UNCONSTRAINED)) {
return 1;
} else if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
return upperBound;
}
}
public static void rotatePicture(String str){
int degree = MessageUtils.getExifOrientation(str);
Bitmap bit = makeBitmap(str);
if(bit != null)
{
Bitmap newBitmap = rotate(bit,degree);
OutputStream stream = null;
try {
stream = new FileOutputStream(str);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if(newBitmap != null){
newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
if(!newBitmap.isRecycled())
{
Log.i(TAG,"newBitmap.isRecycled()");
newBitmap.recycle();
}
}
}
if (bit != null) {
bit.recycle();
bit = null;
}
}
public static Bitmap rotate(Bitmap b, int degrees) {
return rotateAndMirror(b, degrees, false);
}
public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
if ((degrees != 0 || mirror) && b != null) {
Matrix m = new Matrix();
m.setRotate(degrees,
(float) b.getWidth() / 2, (float) b.getHeight() / 2);
if (mirror) {
m.postScale(-1, 1);
degrees = (degrees + 360) % 360;
if (degrees == 0 || degrees == 180) {
m.postTranslate((float) b.getWidth(), 0);
} else if (degrees == 90 || degrees == 270) {
m.postTranslate((float) b.getHeight(), 0);
} else {
throw new IllegalArgumentException("Invalid degrees=" + degrees);
}
}
try {
Bitmap b2 = Bitmap.createBitmap(
b, 0, 0, b.getWidth(), b.getHeight(), m, true);
if (b != b2) {
b.recycle();
b = b2;
}
} catch (OutOfMemoryError ex) {
ex.printStackTrace();
}
}
return b;
}
public static boolean isMSMS = TelephonyManager.getPhoneCount() > 1;
public static int getFileIconId(String fileName) {
if (null == fileName) {
return R.drawable.unknown;
}
int index = fileName.indexOf('.');
if (index == -1) {
return R.drawable.unknown;
} else {
String suffix = fileName.substring(index + 1);
if ("txt".equalsIgnoreCase(suffix)) {
return R.drawable.txt;
} else if ("html".equalsIgnoreCase(suffix) || "htm".equalsIgnoreCase(suffix)) {
return R.drawable.html;
} else if ("xls".equalsIgnoreCase(suffix)) {
return R.drawable.xls;
} else if ("doc".equalsIgnoreCase(suffix)) {
return R.drawable.doc;
} else if ("pdf".equalsIgnoreCase(suffix)) {
return R.drawable.pdf;
} else if ("ppt".equalsIgnoreCase(suffix)) {
return R.drawable.ppt;
} else if ("vcs".equalsIgnoreCase(suffix)) {
return R.drawable.vcs;
} else if ("rar".equalsIgnoreCase(suffix)) {
return R.drawable.rar;
} else if ("zip".equalsIgnoreCase(suffix)) {
return R.drawable.zip;
} else if ("apk".equalsIgnoreCase(suffix)) {
return R.drawable.apk;
} else if ("bin".equalsIgnoreCase(suffix)) {
return R.drawable.bin;
} else {
return R.drawable.unknown;
}
}
}
public static byte[] GetSctsTime(Time time) {
Calendar rightNow = Calendar.getInstance();
byte[] data = new byte[7];
int year = time.year > 2000 ? time.year - 2000 : time.year -1900;
data[0] = (byte) (year);
data[1] = (byte) (time.month+1);
data[2] = (byte) (time.monthDay);
data[3] = (byte) (time.hour);
data[4] = (byte) (time.minute);
data[5] = (byte) (time.second);
int tmp = rightNow.getTimeZone().getOffset(rightNow.getTimeInMillis());
data[6] = (byte)(tmp / 1000 / 900);
for (int i = 0; i < 6; i++) {
data[i] = (byte) (((data[i] / 10) & 0xF) | ((data[i] % 10) << 4));
}
tmp = Math.abs(data[6]);
tmp = ((tmp / 10) & 0xF) | ((tmp % 10) << 4);
if (data[6] < 0) {
tmp |= 0x8;
}
data[6] = (byte)tmp;
return data;
}
public static boolean hasAngel(final Context context, final Uri imageUri) {
UriImage image = new UriImage(context, imageUri);
if (image != null && image.getOrientation() != 0) {
return true;
}
return false;
}
}