From d355153dcbc125b80113c31dafc80afe1a5fddd6 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Mon, 23 Sep 2019 17:59:57 -0700 Subject: Create frameworks/base/telephony/common for common non-mainline telephony code. And move mms util code to the new folder. Test: basic sanity Bug: 140763963 Change-Id: I0d92ed3fca1fc186484ea8d3c5d17b6e332e7d22 --- .../common/com/google/android/mms/ContentType.java | 247 +++ .../android/mms/InvalidHeaderValueException.java | 44 + .../com/google/android/mms/MmsException.java | 66 + .../common/com/google/android/mms/package.html | 5 + .../com/google/android/mms/pdu/AcknowledgeInd.java | 95 + .../common/com/google/android/mms/pdu/Base64.java | 170 ++ .../com/google/android/mms/pdu/CharacterSets.java | 176 ++ .../com/google/android/mms/pdu/DeliveryInd.java | 145 ++ .../google/android/mms/pdu/EncodedStringValue.java | 298 +++ .../com/google/android/mms/pdu/GenericPdu.java | 122 ++ .../android/mms/pdu/MultimediaMessagePdu.java | 164 ++ .../google/android/mms/pdu/NotificationInd.java | 307 +++ .../com/google/android/mms/pdu/NotifyRespInd.java | 121 ++ .../common/com/google/android/mms/pdu/PduBody.java | 204 ++ .../com/google/android/mms/pdu/PduComposer.java | 1229 ++++++++++++ .../google/android/mms/pdu/PduContentTypes.java | 113 ++ .../com/google/android/mms/pdu/PduHeaders.java | 733 +++++++ .../com/google/android/mms/pdu/PduParser.java | 2023 ++++++++++++++++++++ .../common/com/google/android/mms/pdu/PduPart.java | 439 +++++ .../com/google/android/mms/pdu/PduPersister.java | 1573 +++++++++++++++ .../google/android/mms/pdu/QuotedPrintable.java | 71 + .../com/google/android/mms/pdu/ReadOrigInd.java | 158 ++ .../com/google/android/mms/pdu/ReadRecInd.java | 150 ++ .../com/google/android/mms/pdu/RetrieveConf.java | 324 ++++ .../com/google/android/mms/pdu/SendConf.java | 124 ++ .../common/com/google/android/mms/pdu/SendReq.java | 370 ++++ .../common/com/google/android/mms/pdu/package.html | 5 + .../com/google/android/mms/util/AbstractCache.java | 119 ++ .../google/android/mms/util/DownloadDrmHelper.java | 115 ++ .../google/android/mms/util/DrmConvertSession.java | 177 ++ .../com/google/android/mms/util/PduCache.java | 268 +++ .../com/google/android/mms/util/PduCacheEntry.java | 50 + .../com/google/android/mms/util/SqliteWrapper.java | 128 ++ .../com/google/android/mms/util/package.html | 5 + 34 files changed, 10338 insertions(+) create mode 100644 telephony/common/com/google/android/mms/ContentType.java create mode 100644 telephony/common/com/google/android/mms/InvalidHeaderValueException.java create mode 100644 telephony/common/com/google/android/mms/MmsException.java create mode 100755 telephony/common/com/google/android/mms/package.html create mode 100644 telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java create mode 100644 telephony/common/com/google/android/mms/pdu/Base64.java create mode 100644 telephony/common/com/google/android/mms/pdu/CharacterSets.java create mode 100644 telephony/common/com/google/android/mms/pdu/DeliveryInd.java create mode 100644 telephony/common/com/google/android/mms/pdu/EncodedStringValue.java create mode 100644 telephony/common/com/google/android/mms/pdu/GenericPdu.java create mode 100644 telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java create mode 100644 telephony/common/com/google/android/mms/pdu/NotificationInd.java create mode 100644 telephony/common/com/google/android/mms/pdu/NotifyRespInd.java create mode 100644 telephony/common/com/google/android/mms/pdu/PduBody.java create mode 100644 telephony/common/com/google/android/mms/pdu/PduComposer.java create mode 100644 telephony/common/com/google/android/mms/pdu/PduContentTypes.java create mode 100644 telephony/common/com/google/android/mms/pdu/PduHeaders.java create mode 100755 telephony/common/com/google/android/mms/pdu/PduParser.java create mode 100644 telephony/common/com/google/android/mms/pdu/PduPart.java create mode 100755 telephony/common/com/google/android/mms/pdu/PduPersister.java create mode 100644 telephony/common/com/google/android/mms/pdu/QuotedPrintable.java create mode 100644 telephony/common/com/google/android/mms/pdu/ReadOrigInd.java create mode 100644 telephony/common/com/google/android/mms/pdu/ReadRecInd.java create mode 100644 telephony/common/com/google/android/mms/pdu/RetrieveConf.java create mode 100644 telephony/common/com/google/android/mms/pdu/SendConf.java create mode 100644 telephony/common/com/google/android/mms/pdu/SendReq.java create mode 100755 telephony/common/com/google/android/mms/pdu/package.html create mode 100644 telephony/common/com/google/android/mms/util/AbstractCache.java create mode 100644 telephony/common/com/google/android/mms/util/DownloadDrmHelper.java create mode 100644 telephony/common/com/google/android/mms/util/DrmConvertSession.java create mode 100644 telephony/common/com/google/android/mms/util/PduCache.java create mode 100644 telephony/common/com/google/android/mms/util/PduCacheEntry.java create mode 100644 telephony/common/com/google/android/mms/util/SqliteWrapper.java create mode 100755 telephony/common/com/google/android/mms/util/package.html (limited to 'telephony/common') diff --git a/telephony/common/com/google/android/mms/ContentType.java b/telephony/common/com/google/android/mms/ContentType.java new file mode 100644 index 000000000000..12e4b7e26e1e --- /dev/null +++ b/telephony/common/com/google/android/mms/ContentType.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.util.ArrayList; + +public class ContentType { + public static final String MMS_MESSAGE = "application/vnd.wap.mms-message"; + // The phony content type for generic PDUs (e.g. ReadOrig.ind, + // Notification.ind, Delivery.ind). + public static final String MMS_GENERIC = "application/vnd.wap.mms-generic"; + public static final String MULTIPART_MIXED = "application/vnd.wap.multipart.mixed"; + public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related"; + public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative"; + + public static final String TEXT_PLAIN = "text/plain"; + public static final String TEXT_HTML = "text/html"; + public static final String TEXT_VCALENDAR = "text/x-vCalendar"; + public static final String TEXT_VCARD = "text/x-vCard"; + + public static final String IMAGE_UNSPECIFIED = "image/*"; + public static final String IMAGE_JPEG = "image/jpeg"; + public static final String IMAGE_JPG = "image/jpg"; + public static final String IMAGE_GIF = "image/gif"; + public static final String IMAGE_WBMP = "image/vnd.wap.wbmp"; + public static final String IMAGE_PNG = "image/png"; + public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp"; + + public static final String AUDIO_UNSPECIFIED = "audio/*"; + public static final String AUDIO_AAC = "audio/aac"; + public static final String AUDIO_AMR = "audio/amr"; + public static final String AUDIO_IMELODY = "audio/imelody"; + public static final String AUDIO_MID = "audio/mid"; + public static final String AUDIO_MIDI = "audio/midi"; + public static final String AUDIO_MP3 = "audio/mp3"; + public static final String AUDIO_MPEG3 = "audio/mpeg3"; + public static final String AUDIO_MPEG = "audio/mpeg"; + public static final String AUDIO_MPG = "audio/mpg"; + public static final String AUDIO_MP4 = "audio/mp4"; + public static final String AUDIO_X_MID = "audio/x-mid"; + public static final String AUDIO_X_MIDI = "audio/x-midi"; + public static final String AUDIO_X_MP3 = "audio/x-mp3"; + public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3"; + public static final String AUDIO_X_MPEG = "audio/x-mpeg"; + public static final String AUDIO_X_MPG = "audio/x-mpg"; + public static final String AUDIO_3GPP = "audio/3gpp"; + public static final String AUDIO_X_WAV = "audio/x-wav"; + public static final String AUDIO_OGG = "application/ogg"; + public static final String AUDIO_OGG2 = "audio/ogg"; + + public static final String VIDEO_UNSPECIFIED = "video/*"; + public static final String VIDEO_3GPP = "video/3gpp"; + public static final String VIDEO_3G2 = "video/3gpp2"; + public static final String VIDEO_H263 = "video/h263"; + public static final String VIDEO_MP4 = "video/mp4"; + + public static final String APP_SMIL = "application/smil"; + public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml"; + public static final String APP_XHTML = "application/xhtml+xml"; + + public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content"; + public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message"; + + private static final ArrayList sSupportedContentTypes = new ArrayList(); + private static final ArrayList sSupportedImageTypes = new ArrayList(); + private static final ArrayList sSupportedAudioTypes = new ArrayList(); + private static final ArrayList sSupportedVideoTypes = new ArrayList(); + + static { + sSupportedContentTypes.add(TEXT_PLAIN); + sSupportedContentTypes.add(TEXT_HTML); + sSupportedContentTypes.add(TEXT_VCALENDAR); + sSupportedContentTypes.add(TEXT_VCARD); + + sSupportedContentTypes.add(IMAGE_JPEG); + sSupportedContentTypes.add(IMAGE_GIF); + sSupportedContentTypes.add(IMAGE_WBMP); + sSupportedContentTypes.add(IMAGE_PNG); + sSupportedContentTypes.add(IMAGE_JPG); + sSupportedContentTypes.add(IMAGE_X_MS_BMP); + //supportedContentTypes.add(IMAGE_SVG); not yet supported. + + sSupportedContentTypes.add(AUDIO_AAC); + sSupportedContentTypes.add(AUDIO_AMR); + sSupportedContentTypes.add(AUDIO_IMELODY); + sSupportedContentTypes.add(AUDIO_MID); + sSupportedContentTypes.add(AUDIO_MIDI); + sSupportedContentTypes.add(AUDIO_MP3); + sSupportedContentTypes.add(AUDIO_MP4); + sSupportedContentTypes.add(AUDIO_MPEG3); + sSupportedContentTypes.add(AUDIO_MPEG); + sSupportedContentTypes.add(AUDIO_MPG); + sSupportedContentTypes.add(AUDIO_X_MID); + sSupportedContentTypes.add(AUDIO_X_MIDI); + sSupportedContentTypes.add(AUDIO_X_MP3); + sSupportedContentTypes.add(AUDIO_X_MPEG3); + sSupportedContentTypes.add(AUDIO_X_MPEG); + sSupportedContentTypes.add(AUDIO_X_MPG); + sSupportedContentTypes.add(AUDIO_X_WAV); + sSupportedContentTypes.add(AUDIO_3GPP); + sSupportedContentTypes.add(AUDIO_OGG); + sSupportedContentTypes.add(AUDIO_OGG2); + + sSupportedContentTypes.add(VIDEO_3GPP); + sSupportedContentTypes.add(VIDEO_3G2); + sSupportedContentTypes.add(VIDEO_H263); + sSupportedContentTypes.add(VIDEO_MP4); + + sSupportedContentTypes.add(APP_SMIL); + sSupportedContentTypes.add(APP_WAP_XHTML); + sSupportedContentTypes.add(APP_XHTML); + + sSupportedContentTypes.add(APP_DRM_CONTENT); + sSupportedContentTypes.add(APP_DRM_MESSAGE); + + // add supported image types + sSupportedImageTypes.add(IMAGE_JPEG); + sSupportedImageTypes.add(IMAGE_GIF); + sSupportedImageTypes.add(IMAGE_WBMP); + sSupportedImageTypes.add(IMAGE_PNG); + sSupportedImageTypes.add(IMAGE_JPG); + sSupportedImageTypes.add(IMAGE_X_MS_BMP); + + // add supported audio types + sSupportedAudioTypes.add(AUDIO_AAC); + sSupportedAudioTypes.add(AUDIO_AMR); + sSupportedAudioTypes.add(AUDIO_IMELODY); + sSupportedAudioTypes.add(AUDIO_MID); + sSupportedAudioTypes.add(AUDIO_MIDI); + sSupportedAudioTypes.add(AUDIO_MP3); + sSupportedAudioTypes.add(AUDIO_MPEG3); + sSupportedAudioTypes.add(AUDIO_MPEG); + sSupportedAudioTypes.add(AUDIO_MPG); + sSupportedAudioTypes.add(AUDIO_MP4); + sSupportedAudioTypes.add(AUDIO_X_MID); + sSupportedAudioTypes.add(AUDIO_X_MIDI); + sSupportedAudioTypes.add(AUDIO_X_MP3); + sSupportedAudioTypes.add(AUDIO_X_MPEG3); + sSupportedAudioTypes.add(AUDIO_X_MPEG); + sSupportedAudioTypes.add(AUDIO_X_MPG); + sSupportedAudioTypes.add(AUDIO_X_WAV); + sSupportedAudioTypes.add(AUDIO_3GPP); + sSupportedAudioTypes.add(AUDIO_OGG); + sSupportedAudioTypes.add(AUDIO_OGG2); + + // add supported video types + sSupportedVideoTypes.add(VIDEO_3GPP); + sSupportedVideoTypes.add(VIDEO_3G2); + sSupportedVideoTypes.add(VIDEO_H263); + sSupportedVideoTypes.add(VIDEO_MP4); + } + + // This class should never be instantiated. + private ContentType() { + } + + @UnsupportedAppUsage + public static boolean isSupportedType(String contentType) { + return (null != contentType) && sSupportedContentTypes.contains(contentType); + } + + @UnsupportedAppUsage + public static boolean isSupportedImageType(String contentType) { + return isImageType(contentType) && isSupportedType(contentType); + } + + @UnsupportedAppUsage + public static boolean isSupportedAudioType(String contentType) { + return isAudioType(contentType) && isSupportedType(contentType); + } + + @UnsupportedAppUsage + public static boolean isSupportedVideoType(String contentType) { + return isVideoType(contentType) && isSupportedType(contentType); + } + + @UnsupportedAppUsage + public static boolean isTextType(String contentType) { + return (null != contentType) && contentType.startsWith("text/"); + } + + @UnsupportedAppUsage + public static boolean isImageType(String contentType) { + return (null != contentType) && contentType.startsWith("image/"); + } + + @UnsupportedAppUsage + public static boolean isAudioType(String contentType) { + return (null != contentType) && contentType.startsWith("audio/"); + } + + @UnsupportedAppUsage + public static boolean isVideoType(String contentType) { + return (null != contentType) && contentType.startsWith("video/"); + } + + @UnsupportedAppUsage + public static boolean isDrmType(String contentType) { + return (null != contentType) + && (contentType.equals(APP_DRM_CONTENT) + || contentType.equals(APP_DRM_MESSAGE)); + } + + public static boolean isUnspecified(String contentType) { + return (null != contentType) && contentType.endsWith("*"); + } + + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static ArrayList getImageTypes() { + return (ArrayList) sSupportedImageTypes.clone(); + } + + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static ArrayList getAudioTypes() { + return (ArrayList) sSupportedAudioTypes.clone(); + } + + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static ArrayList getVideoTypes() { + return (ArrayList) sSupportedVideoTypes.clone(); + } + + @SuppressWarnings("unchecked") + public static ArrayList getSupportedTypes() { + return (ArrayList) sSupportedContentTypes.clone(); + } +} diff --git a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java new file mode 100644 index 000000000000..2836c3075b3b --- /dev/null +++ b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +/** + * Thrown when an invalid header value was set. + */ +public class InvalidHeaderValueException extends MmsException { + private static final long serialVersionUID = -2053384496042052262L; + + /** + * Constructs an InvalidHeaderValueException with no detailed message. + */ + public InvalidHeaderValueException() { + super(); + } + + /** + * Constructs an InvalidHeaderValueException with the specified detailed message. + * + * @param message the detailed message. + */ + @UnsupportedAppUsage + public InvalidHeaderValueException(String message) { + super(message); + } +} diff --git a/telephony/common/com/google/android/mms/MmsException.java b/telephony/common/com/google/android/mms/MmsException.java new file mode 100644 index 000000000000..5be33ed1fac9 --- /dev/null +++ b/telephony/common/com/google/android/mms/MmsException.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +/** + * A generic exception that is thrown by the Mms client. + */ +public class MmsException extends Exception { + private static final long serialVersionUID = -7323249827281485390L; + + /** + * Creates a new MmsException. + */ + @UnsupportedAppUsage + public MmsException() { + super(); + } + + /** + * Creates a new MmsException with the specified detail message. + * + * @param message the detail message. + */ + @UnsupportedAppUsage + public MmsException(String message) { + super(message); + } + + /** + * Creates a new MmsException with the specified cause. + * + * @param cause the cause. + */ + @UnsupportedAppUsage + public MmsException(Throwable cause) { + super(cause); + } + + /** + * Creates a new MmsException with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause. + */ + @UnsupportedAppUsage + public MmsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/telephony/common/com/google/android/mms/package.html b/telephony/common/com/google/android/mms/package.html new file mode 100755 index 000000000000..c9f96a66ab3b --- /dev/null +++ b/telephony/common/com/google/android/mms/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java new file mode 100644 index 000000000000..ae447d7a7417 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Acknowledge.ind PDU. + */ +public class AcknowledgeInd extends GenericPdu { + /** + * Constructor, used when composing a M-Acknowledge.ind pdu. + * + * @param mmsVersion current viersion of mms + * @param transactionId the transaction-id value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if transactionId is null. + */ + @UnsupportedAppUsage + public AcknowledgeInd(int mmsVersion, byte[] transactionId) + throws InvalidHeaderValueException { + super(); + + setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); + setMmsVersion(mmsVersion); + setTransactionId(transactionId); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + AcknowledgeInd(PduHeaders headers) { + super(headers); + } + + /** + * Get X-Mms-Report-Allowed field value. + * + * @return the X-Mms-Report-Allowed value + */ + public int getReportAllowed() { + return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); + } + + /** + * Set X-Mms-Report-Allowed field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setReportAllowed(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/Base64.java b/telephony/common/com/google/android/mms/pdu/Base64.java new file mode 100644 index 000000000000..483fa7f9842e --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/Base64.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +public class Base64 { + /** + * Used to get the number of Quadruples. + */ + static final int FOURBYTE = 4; + + /** + * Byte used to pad output. + */ + static final byte PAD = (byte) '='; + + /** + * The base length. + */ + static final int BASELENGTH = 255; + + // Create arrays to hold the base64 characters + private static byte[] base64Alphabet = new byte[BASELENGTH]; + + // Populating the character arrays + static { + for (int i = 0; i < BASELENGTH; i++) { + base64Alphabet[i] = (byte) -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + } + + /** + * Decodes Base64 data into octects + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + @UnsupportedAppUsage + public static byte[] decodeBase64(byte[] base64Data) { + // RFC 2045 requires that we discard ALL non-Base64 characters + base64Data = discardNonBase64(base64Data); + + // handle the edge case, so we don't have to worry about it later + if (base64Data.length == 0) { + return new byte[0]; + } + + int numberQuadruple = base64Data.length / FOURBYTE; + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; + + // Throw away anything not in base64Data + + int encodedIndex = 0; + int dataIndex = 0; + { + // this sizes the output array properly - rlw + int lastData = base64Data.length; + // ignore the '=' padding + while (base64Data[lastData - 1] == PAD) { + if (--lastData == 0) { + return new byte[0]; + } + } + decodedData = new byte[lastData - numberQuadruple]; + } + + for (int i = 0; i < numberQuadruple; i++) { + dataIndex = i * 4; + marker0 = base64Data[dataIndex + 2]; + marker1 = base64Data[dataIndex + 3]; + + b1 = base64Alphabet[base64Data[dataIndex]]; + b2 = base64Alphabet[base64Data[dataIndex + 1]]; + + if (marker0 != PAD && marker1 != PAD) { + //No PAD e.g 3cQl + b3 = base64Alphabet[marker0]; + b4 = base64Alphabet[marker1]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); + } else if (marker0 == PAD) { + //Two PAD e.g. 3c[Pad][Pad] + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + } else if (marker1 == PAD) { + //One PAD e.g. 3cQ[Pad] + b3 = base64Alphabet[marker0]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + } + encodedIndex += 3; + } + return decodedData; + } + + /** + * Check octect whether it is a base64 encoding. + * + * @param octect to be checked byte + * @return ture if it is base64 encoding, false otherwise. + */ + private static boolean isBase64(byte octect) { + if (octect == PAD) { + return true; + } else if (base64Alphabet[octect] == -1) { + return false; + } else { + return true; + } + } + + /** + * Discards any characters outside of the base64 alphabet, per + * the requirements on page 25 of RFC 2045 - "Any characters + * outside of the base64 alphabet are to be ignored in base64 + * encoded data." + * + * @param data The base-64 encoded data to groom + * @return The data, less non-base64 characters (see RFC 2045). + */ + static byte[] discardNonBase64(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + if (isBase64(data[i])) { + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } +} diff --git a/telephony/common/com/google/android/mms/pdu/CharacterSets.java b/telephony/common/com/google/android/mms/pdu/CharacterSets.java new file mode 100644 index 000000000000..27da35e2d928 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/CharacterSets.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; + +public class CharacterSets { + /** + * IANA assigned MIB enum numbers. + * + * From wap-230-wsp-20010705-a.pdf + * Any-charset = + * Equivalent to the special RFC2616 charset value "*" + */ + public static final int ANY_CHARSET = 0x00; + public static final int US_ASCII = 0x03; + public static final int ISO_8859_1 = 0x04; + public static final int ISO_8859_2 = 0x05; + public static final int ISO_8859_3 = 0x06; + public static final int ISO_8859_4 = 0x07; + public static final int ISO_8859_5 = 0x08; + public static final int ISO_8859_6 = 0x09; + public static final int ISO_8859_7 = 0x0A; + public static final int ISO_8859_8 = 0x0B; + public static final int ISO_8859_9 = 0x0C; + public static final int SHIFT_JIS = 0x11; + public static final int UTF_8 = 0x6A; + public static final int BIG5 = 0x07EA; + public static final int UCS2 = 0x03E8; + public static final int UTF_16 = 0x03F7; + + /** + * If the encoding of given data is unsupported, use UTF_8 to decode it. + */ + public static final int DEFAULT_CHARSET = UTF_8; + + /** + * Array of MIB enum numbers. + */ + private static final int[] MIBENUM_NUMBERS = { + ANY_CHARSET, + US_ASCII, + ISO_8859_1, + ISO_8859_2, + ISO_8859_3, + ISO_8859_4, + ISO_8859_5, + ISO_8859_6, + ISO_8859_7, + ISO_8859_8, + ISO_8859_9, + SHIFT_JIS, + UTF_8, + BIG5, + UCS2, + UTF_16, + }; + + /** + * The Well-known-charset Mime name. + */ + public static final String MIMENAME_ANY_CHARSET = "*"; + public static final String MIMENAME_US_ASCII = "us-ascii"; + public static final String MIMENAME_ISO_8859_1 = "iso-8859-1"; + public static final String MIMENAME_ISO_8859_2 = "iso-8859-2"; + public static final String MIMENAME_ISO_8859_3 = "iso-8859-3"; + public static final String MIMENAME_ISO_8859_4 = "iso-8859-4"; + public static final String MIMENAME_ISO_8859_5 = "iso-8859-5"; + public static final String MIMENAME_ISO_8859_6 = "iso-8859-6"; + public static final String MIMENAME_ISO_8859_7 = "iso-8859-7"; + public static final String MIMENAME_ISO_8859_8 = "iso-8859-8"; + public static final String MIMENAME_ISO_8859_9 = "iso-8859-9"; + public static final String MIMENAME_SHIFT_JIS = "shift_JIS"; + public static final String MIMENAME_UTF_8 = "utf-8"; + public static final String MIMENAME_BIG5 = "big5"; + public static final String MIMENAME_UCS2 = "iso-10646-ucs-2"; + public static final String MIMENAME_UTF_16 = "utf-16"; + + public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8; + + /** + * Array of the names of character sets. + */ + private static final String[] MIME_NAMES = { + MIMENAME_ANY_CHARSET, + MIMENAME_US_ASCII, + MIMENAME_ISO_8859_1, + MIMENAME_ISO_8859_2, + MIMENAME_ISO_8859_3, + MIMENAME_ISO_8859_4, + MIMENAME_ISO_8859_5, + MIMENAME_ISO_8859_6, + MIMENAME_ISO_8859_7, + MIMENAME_ISO_8859_8, + MIMENAME_ISO_8859_9, + MIMENAME_SHIFT_JIS, + MIMENAME_UTF_8, + MIMENAME_BIG5, + MIMENAME_UCS2, + MIMENAME_UTF_16, + }; + + private static final HashMap MIBENUM_TO_NAME_MAP; + private static final HashMap NAME_TO_MIBENUM_MAP; + + static { + // Create the HashMaps. + MIBENUM_TO_NAME_MAP = new HashMap(); + NAME_TO_MIBENUM_MAP = new HashMap(); + assert(MIBENUM_NUMBERS.length == MIME_NAMES.length); + int count = MIBENUM_NUMBERS.length - 1; + for(int i = 0; i <= count; i++) { + MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]); + NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]); + } + } + + private CharacterSets() {} // Non-instantiatable + + /** + * Map an MIBEnum number to the name of the charset which this number + * is assigned to by IANA. + * + * @param mibEnumValue An IANA assigned MIBEnum number. + * @return The name string of the charset. + * @throws UnsupportedEncodingException + */ + @UnsupportedAppUsage + public static String getMimeName(int mibEnumValue) + throws UnsupportedEncodingException { + String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue); + if (name == null) { + throw new UnsupportedEncodingException(); + } + return name; + } + + /** + * Map a well-known charset name to its assigned MIBEnum number. + * + * @param mimeName The charset name. + * @return The MIBEnum number assigned by IANA for this charset. + * @throws UnsupportedEncodingException + */ + @UnsupportedAppUsage + public static int getMibEnumValue(String mimeName) + throws UnsupportedEncodingException { + if(null == mimeName) { + return -1; + } + + Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName); + if (mibEnumValue == null) { + throw new UnsupportedEncodingException(); + } + return mibEnumValue; + } +} diff --git a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java new file mode 100644 index 000000000000..7093ac63338c --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Delivery.Ind Pdu. + */ +public class DeliveryInd extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + public DeliveryInd() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + DeliveryInd(PduHeaders headers) { + super(headers); + } + + /** + * Get Date value. + * + * @return the value + */ + @UnsupportedAppUsage + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value. + * + * @param value the value + */ + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value, should not be null + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get Status value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getStatus() { + return mPduHeaders.getOctet(PduHeaders.STATUS); + } + + /** + * Set Status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.STATUS); + } + + /** + * Get To value. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * set To value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public EncodedStringValue getStatusText() {return null;} + * public void setStatusText(EncodedStringValue value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java new file mode 100644 index 000000000000..41662750842f --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; + +/** + * Encoded-string-value = Text-string | Value-length Char-set Text-string + */ +public class EncodedStringValue implements Cloneable { + private static final String TAG = "EncodedStringValue"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + /** + * The Char-set value. + */ + private int mCharacterSet; + + /** + * The Text-string value. + */ + private byte[] mData; + + /** + * Constructor. + * + * @param charset the Char-set value + * @param data the Text-string value + * @throws NullPointerException if Text-string value is null. + */ + @UnsupportedAppUsage + public EncodedStringValue(int charset, byte[] data) { + // TODO: CharSet needs to be validated against MIBEnum. + if(null == data) { + throw new NullPointerException("EncodedStringValue: Text-string is null."); + } + + mCharacterSet = charset; + mData = new byte[data.length]; + System.arraycopy(data, 0, mData, 0, data.length); + } + + /** + * Constructor. + * + * @param data the Text-string value + * @throws NullPointerException if Text-string value is null. + */ + @UnsupportedAppUsage + public EncodedStringValue(byte[] data) { + this(CharacterSets.DEFAULT_CHARSET, data); + } + + @UnsupportedAppUsage + public EncodedStringValue(String data) { + try { + mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME); + mCharacterSet = CharacterSets.DEFAULT_CHARSET; + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Default encoding must be supported.", e); + } + } + + /** + * Get Char-set value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getCharacterSet() { + return mCharacterSet; + } + + /** + * Set Char-set value. + * + * @param charset the Char-set value + */ + @UnsupportedAppUsage + public void setCharacterSet(int charset) { + // TODO: CharSet needs to be validated against MIBEnum. + mCharacterSet = charset; + } + + /** + * Get Text-string value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getTextString() { + byte[] byteArray = new byte[mData.length]; + + System.arraycopy(mData, 0, byteArray, 0, mData.length); + return byteArray; + } + + /** + * Set Text-string value. + * + * @param textString the Text-string value + * @throws NullPointerException if Text-string value is null. + */ + @UnsupportedAppUsage + public void setTextString(byte[] textString) { + if(null == textString) { + throw new NullPointerException("EncodedStringValue: Text-string is null."); + } + + mData = new byte[textString.length]; + System.arraycopy(textString, 0, mData, 0, textString.length); + } + + /** + * Convert this object to a {@link java.lang.String}. If the encoding of + * the EncodedStringValue is null or unsupported, it will be + * treated as iso-8859-1 encoding. + * + * @return The decoded String. + */ + @UnsupportedAppUsage + public String getString() { + if (CharacterSets.ANY_CHARSET == mCharacterSet) { + return new String(mData); // system default encoding. + } else { + try { + String name = CharacterSets.getMimeName(mCharacterSet); + return new String(mData, name); + } catch (UnsupportedEncodingException e) { + if (LOCAL_LOGV) { + Log.v(TAG, e.getMessage(), e); + } + try { + return new String(mData, CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e2) { + return new String(mData); // system default encoding. + } + } + } + } + + /** + * Append to Text-string. + * + * @param textString the textString to append + * @throws NullPointerException if the text String is null + * or an IOException occurred. + */ + @UnsupportedAppUsage + public void appendTextString(byte[] textString) { + if(null == textString) { + throw new NullPointerException("Text-string is null."); + } + + if(null == mData) { + mData = new byte[textString.length]; + System.arraycopy(textString, 0, mData, 0, textString.length); + } else { + ByteArrayOutputStream newTextString = new ByteArrayOutputStream(); + try { + newTextString.write(mData); + newTextString.write(textString); + } catch (IOException e) { + e.printStackTrace(); + throw new NullPointerException( + "appendTextString: failed when write a new Text-string"); + } + + mData = newTextString.toByteArray(); + } + } + + /* + * (non-Javadoc) + * @see java.lang.Object#clone() + */ + @Override + public Object clone() throws CloneNotSupportedException { + super.clone(); + int len = mData.length; + byte[] dstBytes = new byte[len]; + System.arraycopy(mData, 0, dstBytes, 0, len); + + try { + return new EncodedStringValue(mCharacterSet, dstBytes); + } catch (Exception e) { + Log.e(TAG, "failed to clone an EncodedStringValue: " + this); + e.printStackTrace(); + throw new CloneNotSupportedException(e.getMessage()); + } + } + + /** + * Split this encoded string around matches of the given pattern. + * + * @param pattern the delimiting pattern + * @return the array of encoded strings computed by splitting this encoded + * string around matches of the given pattern + */ + public EncodedStringValue[] split(String pattern) { + String[] temp = getString().split(pattern); + EncodedStringValue[] ret = new EncodedStringValue[temp.length]; + for (int i = 0; i < ret.length; ++i) { + try { + ret[i] = new EncodedStringValue(mCharacterSet, + temp[i].getBytes()); + } catch (NullPointerException e) { + // Can't arrive here + return null; + } + } + return ret; + } + + /** + * Extract an EncodedStringValue[] from a given String. + */ + @UnsupportedAppUsage + public static EncodedStringValue[] extract(String src) { + String[] values = src.split(";"); + + ArrayList list = new ArrayList(); + for (int i = 0; i < values.length; i++) { + if (values[i].length() > 0) { + list.add(new EncodedStringValue(values[i])); + } + } + + int len = list.size(); + if (len > 0) { + return list.toArray(new EncodedStringValue[len]); + } else { + return null; + } + } + + /** + * Concatenate an EncodedStringValue[] into a single String. + */ + @UnsupportedAppUsage + public static String concat(EncodedStringValue[] addr) { + StringBuilder sb = new StringBuilder(); + int maxIndex = addr.length - 1; + for (int i = 0; i <= maxIndex; i++) { + sb.append(addr[i].getString()); + if (i < maxIndex) { + sb.append(";"); + } + } + + return sb.toString(); + } + + @UnsupportedAppUsage + public static EncodedStringValue copy(EncodedStringValue value) { + if (value == null) { + return null; + } + + return new EncodedStringValue(value.mCharacterSet, value.mData); + } + + @UnsupportedAppUsage + public static EncodedStringValue[] encodeStrings(String[] array) { + int count = array.length; + if (count > 0) { + EncodedStringValue[] encodedArray = new EncodedStringValue[count]; + for (int i = 0; i < count; i++) { + encodedArray[i] = new EncodedStringValue(array[i]); + } + return encodedArray; + } + return null; + } +} diff --git a/telephony/common/com/google/android/mms/pdu/GenericPdu.java b/telephony/common/com/google/android/mms/pdu/GenericPdu.java new file mode 100644 index 000000000000..ebf16ac7e632 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/GenericPdu.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +public class GenericPdu { + /** + * The headers of pdu. + */ + @UnsupportedAppUsage + PduHeaders mPduHeaders = null; + + /** + * Constructor. + */ + @UnsupportedAppUsage + public GenericPdu() { + mPduHeaders = new PduHeaders(); + } + + /** + * Constructor. + * + * @param headers Headers for this PDU. + */ + GenericPdu(PduHeaders headers) { + mPduHeaders = headers; + } + + /** + * Get the headers of this PDU. + * + * @return A PduHeaders of this PDU. + */ + @UnsupportedAppUsage + PduHeaders getPduHeaders() { + return mPduHeaders; + } + + /** + * Get X-Mms-Message-Type field value. + * + * @return the X-Mms-Report-Allowed value + */ + @UnsupportedAppUsage + public int getMessageType() { + return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE); + } + + /** + * Set X-Mms-Message-Type field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if field's value is not Octet. + */ + @UnsupportedAppUsage + public void setMessageType(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE); + } + + /** + * Get X-Mms-MMS-Version field value. + * + * @return the X-Mms-MMS-Version value + */ + public int getMmsVersion() { + return mPduHeaders.getOctet(PduHeaders.MMS_VERSION); + } + + /** + * Set X-Mms-MMS-Version field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if field's value is not Octet. + */ + public void setMmsVersion(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java new file mode 100644 index 000000000000..e108f7600baf --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * Multimedia message PDU. + */ +public class MultimediaMessagePdu extends GenericPdu{ + /** + * The body. + */ + private PduBody mMessageBody; + + /** + * Constructor. + */ + @UnsupportedAppUsage + public MultimediaMessagePdu() { + super(); + } + + /** + * Constructor. + * + * @param header the header of this PDU + * @param body the body of this PDU + */ + @UnsupportedAppUsage + public MultimediaMessagePdu(PduHeaders header, PduBody body) { + super(header); + mMessageBody = body; + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + MultimediaMessagePdu(PduHeaders headers) { + super(headers); + } + + /** + * Get body of the PDU. + * + * @return the body + */ + @UnsupportedAppUsage + public PduBody getBody() { + return mMessageBody; + } + + /** + * Set body of the PDU. + * + * @param body the body + */ + @UnsupportedAppUsage + public void setBody(PduBody body) { + mMessageBody = body; + } + + /** + * Get subject. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue getSubject() { + return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); + } + + /** + * Set subject. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setSubject(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); + } + + /** + * Get To value. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * Add a "To" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void addTo(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO); + } + + /** + * Get X-Mms-Priority value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getPriority() { + return mPduHeaders.getOctet(PduHeaders.PRIORITY); + } + + /** + * Set X-Mms-Priority value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setPriority(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.PRIORITY); + } + + /** + * Get Date value. + * + * @return the value + */ + @UnsupportedAppUsage + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value in seconds. + * + * @param value the value + */ + @UnsupportedAppUsage + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/NotificationInd.java b/telephony/common/com/google/android/mms/pdu/NotificationInd.java new file mode 100644 index 000000000000..b561bd4ab3a7 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/NotificationInd.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Notification.ind PDU. + */ +public class NotificationInd extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public NotificationInd() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + NotificationInd(PduHeaders headers) { + super(headers); + } + + /** + * Get X-Mms-Content-Class Value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getContentClass() { + return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS); + } + + /** + * Set X-Mms-Content-Class Value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setContentClass(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS); + } + + /** + * Get X-Mms-Content-Location value. + * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: + * Content-location-value = Uri-value + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentLocation() { + return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION); + } + + /** + * Set X-Mms-Content-Location value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setContentLocation(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION); + } + + /** + * Get X-Mms-Expiry value. + * + * Expiry-value = Value-length + * (Absolute-token Date-value | Relative-token Delta-seconds-value) + * + * @return the value + */ + @UnsupportedAppUsage + public long getExpiry() { + return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); + } + + /** + * Set X-Mms-Expiry value. + * + * @param value the value + * @throws RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setExpiry(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } + + /** + * Get X-Mms-Message-Class value. + * Message-class-value = Class-identifier | Token-text + * Class-identifier = Personal | Advertisement | Informational | Auto + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageClass() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); + } + + /** + * Set X-Mms-Message-Class value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setMessageClass(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); + } + + /** + * Get X-Mms-Message-Size value. + * Message-size-value = Long-integer + * + * @return the value + */ + @UnsupportedAppUsage + public long getMessageSize() { + return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); + } + + /** + * Set X-Mms-Message-Size value. + * + * @param value the value + * @throws RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setMessageSize(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); + } + + /** + * Get subject. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue getSubject() { + return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); + } + + /** + * Set subject. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setSubject(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); + } + + /** + * Get X-Mms-Transaction-Id. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /** + * Get X-Mms-Delivery-Report Value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getDeliveryReport() { + return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); + } + + /** + * Set X-Mms-Delivery-Report Value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setDeliveryReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte getDrmContent() {return 0x00;} + * public void setDrmContent(byte value) {} + * + * public byte getDistributionIndicator() {return 0x00;} + * public void setDistributionIndicator(byte value) {} + * + * public ElementDescriptorValue getElementDescriptor() {return null;} + * public void getElementDescriptor(ElementDescriptorValue value) {} + * + * public byte getPriority() {return 0x00;} + * public void setPriority(byte value) {} + * + * public byte getRecommendedRetrievalMode() {return 0x00;} + * public void setRecommendedRetrievalMode(byte value) {} + * + * public byte getRecommendedRetrievalModeText() {return 0x00;} + * public void setRecommendedRetrievalModeText(byte value) {} + * + * public byte[] getReplaceId() {return 0x00;} + * public void setReplaceId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getReplyCharging() {return 0x00;} + * public void setReplyCharging(byte value) {} + * + * public byte getReplyChargingDeadline() {return 0x00;} + * public void setReplyChargingDeadline(byte value) {} + * + * public byte[] getReplyChargingId() {return 0x00;} + * public void setReplyChargingId(byte[] value) {} + * + * public long getReplyChargingSize() {return 0;} + * public void setReplyChargingSize(long value) {} + * + * public byte getStored() {return 0x00;} + * public void setStored(byte value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java new file mode 100644 index 000000000000..3c70f86a0890 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-NofifyResp.ind PDU. + */ +public class NotifyRespInd extends GenericPdu { + /** + * Constructor, used when composing a M-NotifyResp.ind pdu. + * + * @param mmsVersion current version of mms + * @param transactionId the transaction-id value + * @param status the status value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if transactionId is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public NotifyRespInd(int mmsVersion, + byte[] transactionId, + int status) throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); + setMmsVersion(mmsVersion); + setTransactionId(transactionId); + setStatus(status); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + NotifyRespInd(PduHeaders headers) { + super(headers); + } + + /** + * Get X-Mms-Report-Allowed field value. + * + * @return the X-Mms-Report-Allowed value + */ + public int getReportAllowed() { + return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); + } + + /** + * Set X-Mms-Report-Allowed field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setReportAllowed(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); + } + + /** + * Set X-Mms-Status field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.STATUS); + } + + /** + * GetX-Mms-Status field value. + * + * @return the X-Mms-Status value + */ + public int getStatus() { + return mPduHeaders.getOctet(PduHeaders.STATUS); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + @UnsupportedAppUsage + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/PduBody.java b/telephony/common/com/google/android/mms/pdu/PduBody.java new file mode 100644 index 000000000000..51914e4110b0 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduBody.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +public class PduBody { + private Vector mParts = null; + + private Map mPartMapByContentId = null; + private Map mPartMapByContentLocation = null; + private Map mPartMapByName = null; + private Map mPartMapByFileName = null; + + /** + * Constructor. + */ + @UnsupportedAppUsage + public PduBody() { + mParts = new Vector(); + + mPartMapByContentId = new HashMap(); + mPartMapByContentLocation = new HashMap(); + mPartMapByName = new HashMap(); + mPartMapByFileName = new HashMap(); + } + + private void putPartToMaps(PduPart part) { + // Put part to mPartMapByContentId. + byte[] contentId = part.getContentId(); + if(null != contentId) { + mPartMapByContentId.put(new String(contentId), part); + } + + // Put part to mPartMapByContentLocation. + byte[] contentLocation = part.getContentLocation(); + if(null != contentLocation) { + String clc = new String(contentLocation); + mPartMapByContentLocation.put(clc, part); + } + + // Put part to mPartMapByName. + byte[] name = part.getName(); + if(null != name) { + String clc = new String(name); + mPartMapByName.put(clc, part); + } + + // Put part to mPartMapByFileName. + byte[] fileName = part.getFilename(); + if(null != fileName) { + String clc = new String(fileName); + mPartMapByFileName.put(clc, part); + } + } + + /** + * Appends the specified part to the end of this body. + * + * @param part part to be appended + * @return true when success, false when fail + * @throws NullPointerException when part is null + */ + @UnsupportedAppUsage + public boolean addPart(PduPart part) { + if(null == part) { + throw new NullPointerException(); + } + + putPartToMaps(part); + return mParts.add(part); + } + + /** + * Inserts the specified part at the specified position. + * + * @param index index at which the specified part is to be inserted + * @param part part to be inserted + * @throws NullPointerException when part is null + */ + @UnsupportedAppUsage + public void addPart(int index, PduPart part) { + if(null == part) { + throw new NullPointerException(); + } + + putPartToMaps(part); + mParts.add(index, part); + } + + /** + * Removes the part at the specified position. + * + * @param index index of the part to return + * @return part at the specified index + */ + @UnsupportedAppUsage + public PduPart removePart(int index) { + return mParts.remove(index); + } + + /** + * Remove all of the parts. + */ + public void removeAll() { + mParts.clear(); + } + + /** + * Get the part at the specified position. + * + * @param index index of the part to return + * @return part at the specified index + */ + @UnsupportedAppUsage + public PduPart getPart(int index) { + return mParts.get(index); + } + + /** + * Get the index of the specified part. + * + * @param part the part object + * @return index the index of the first occurrence of the part in this body + */ + @UnsupportedAppUsage + public int getPartIndex(PduPart part) { + return mParts.indexOf(part); + } + + /** + * Get the number of parts. + * + * @return the number of parts + */ + @UnsupportedAppUsage + public int getPartsNum() { + return mParts.size(); + } + + /** + * Get pdu part by content id. + * + * @param cid the value of content id. + * @return the pdu part. + */ + @UnsupportedAppUsage + public PduPart getPartByContentId(String cid) { + return mPartMapByContentId.get(cid); + } + + /** + * Get pdu part by Content-Location. Content-Location of part is + * the same as filename and name(param of content-type). + * + * @param fileName the value of filename. + * @return the pdu part. + */ + @UnsupportedAppUsage + public PduPart getPartByContentLocation(String contentLocation) { + return mPartMapByContentLocation.get(contentLocation); + } + + /** + * Get pdu part by name. + * + * @param fileName the value of filename. + * @return the pdu part. + */ + @UnsupportedAppUsage + public PduPart getPartByName(String name) { + return mPartMapByName.get(name); + } + + /** + * Get pdu part by filename. + * + * @param fileName the value of filename. + * @return the pdu part. + */ + @UnsupportedAppUsage + public PduPart getPartByFileName(String filename) { + return mPartMapByFileName.get(filename); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/PduComposer.java b/telephony/common/com/google/android/mms/pdu/PduComposer.java new file mode 100644 index 000000000000..e24bf21a11b5 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduComposer.java @@ -0,0 +1,1229 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.content.ContentResolver; +import android.content.Context; +import android.text.TextUtils; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; + +public class PduComposer { + /** + * Address type. + */ + static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1; + static private final int PDU_EMAIL_ADDRESS_TYPE = 2; + static private final int PDU_IPV4_ADDRESS_TYPE = 3; + static private final int PDU_IPV6_ADDRESS_TYPE = 4; + static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5; + + /** + * Address regular expression string. + */ + static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+"; + static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" + + "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}"; + static final String REGEXP_IPV6_ADDRESS_TYPE = + "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + + "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + + "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}"; + static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" + + "[0-9]{1,3}\\.{1}[0-9]{1,3}"; + + /** + * The postfix strings of address. + */ + static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN"; + static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4"; + static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6"; + + /** + * Error values. + */ + static private final int PDU_COMPOSE_SUCCESS = 0; + static private final int PDU_COMPOSE_CONTENT_ERROR = 1; + static private final int PDU_COMPOSE_FIELD_NOT_SET = 2; + static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3; + + /** + * WAP values defined in WSP spec. + */ + static private final int QUOTED_STRING_FLAG = 34; + static private final int END_STRING_FLAG = 0; + static private final int LENGTH_QUOTE = 31; + static private final int TEXT_MAX = 127; + static private final int SHORT_INTEGER_MAX = 127; + static private final int LONG_INTEGER_LENGTH_MAX = 8; + + /** + * Block size when read data from InputStream. + */ + static private final int PDU_COMPOSER_BLOCK_SIZE = 1024; + + /** + * The output message. + */ + @UnsupportedAppUsage + protected ByteArrayOutputStream mMessage = null; + + /** + * The PDU. + */ + @UnsupportedAppUsage + private GenericPdu mPdu = null; + + /** + * Current visiting position of the mMessage. + */ + @UnsupportedAppUsage + protected int mPosition = 0; + + /** + * Message compose buffer stack. + */ + @UnsupportedAppUsage + private BufferStack mStack = null; + + /** + * Content resolver. + */ + @UnsupportedAppUsage + private final ContentResolver mResolver; + + /** + * Header of this pdu. + */ + @UnsupportedAppUsage + private PduHeaders mPduHeader = null; + + /** + * Map of all content type + */ + @UnsupportedAppUsage + private static HashMap mContentTypeMap = null; + + static { + mContentTypeMap = new HashMap(); + + int i; + for (i = 0; i < PduContentTypes.contentTypes.length; i++) { + mContentTypeMap.put(PduContentTypes.contentTypes[i], i); + } + } + + /** + * Constructor. + * + * @param context the context + * @param pdu the pdu to be composed + */ + @UnsupportedAppUsage + public PduComposer(Context context, GenericPdu pdu) { + mPdu = pdu; + mResolver = context.getContentResolver(); + mPduHeader = pdu.getPduHeaders(); + mStack = new BufferStack(); + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + /** + * Make the message. No need to check whether mandatory fields are set, + * because the constructors of outgoing pdus are taking care of this. + * + * @return OutputStream of maked message. Return null if + * the PDU is invalid. + */ + @UnsupportedAppUsage + public byte[] make() { + // Get Message-type. + int type = mPdu.getMessageType(); + + /* make the message */ + switch (type) { + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + if (makeSendRetrievePdu(type) != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + if (makeAckInd() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + default: + return null; + } + + return mMessage.toByteArray(); + } + + /** + * Copy buf to mMessage. + */ + @UnsupportedAppUsage + protected void arraycopy(byte[] buf, int pos, int length) { + mMessage.write(buf, pos, length); + mPosition = mPosition + length; + } + + /** + * Append a byte to mMessage. + */ + protected void append(int value) { + mMessage.write(value); + mPosition ++; + } + + /** + * Append short integer value to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendShortInteger(int value) { + /* + * From WAP-230-WSP-20010705-a: + * Short-integer = OCTET + * ; Integers in range 0-127 shall be encoded as a one octet value + * ; with the most significant bit set to one (1xxx xxxx) and with + * ; the value in the remaining least significant bits. + * In our implementation, only low 7 bits are stored and otherwise + * bits are ignored. + */ + append((value | 0x80) & 0xff); + } + + /** + * Append an octet number between 128 and 255 into mMessage. + * NOTE: + * A value between 0 and 127 should be appended by using appendShortInteger. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendOctet(int number) { + append(number); + } + + /** + * Append a short length into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendShortLength(int value) { + /* + * From WAP-230-WSP-20010705-a: + * Short-length = + */ + append(value); + } + + /** + * Append long integer into mMessage. it's used for really long integers. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendLongInteger(long longInt) { + /* + * From WAP-230-WSP-20010705-a: + * Long-integer = Short-length Multi-octet-integer + * ; The Short-length indicates the length of the Multi-octet-integer + * Multi-octet-integer = 1*30 OCTET + * ; The content octets shall be an unsigned integer value with the + * ; most significant octet encoded first (big-endian representation). + * ; The minimum number of octets must be used to encode the value. + */ + int size; + long temp = longInt; + + // Count the length of the long integer. + for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) { + temp = (temp >>> 8); + } + + // Set Length. + appendShortLength(size); + + // Count and set the long integer. + int i; + int shift = (size -1) * 8; + + for (i = 0; i < size; i++) { + append((int)((longInt >>> shift) & 0xff)); + shift = shift - 8; + } + } + + /** + * Append text string into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendTextString(byte[] text) { + /* + * From WAP-230-WSP-20010705-a: + * Text-string = [Quote] *TEXT End-of-string + * ; If the first character in the TEXT is in the range of 128-255, + * ; a Quote character must precede it. Otherwise the Quote character + * ;must be omitted. The Quote is not part of the contents. + */ + if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255 + append(TEXT_MAX); + } + + arraycopy(text, 0, text.length); + append(0); + } + + /** + * Append text string into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendTextString(String str) { + /* + * From WAP-230-WSP-20010705-a: + * Text-string = [Quote] *TEXT End-of-string + * ; If the first character in the TEXT is in the range of 128-255, + * ; a Quote character must precede it. Otherwise the Quote character + * ;must be omitted. The Quote is not part of the contents. + */ + appendTextString(str.getBytes()); + } + + /** + * Append encoded string value to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendEncodedString(EncodedStringValue enStr) { + /* + * From OMA-TS-MMS-ENC-V1_3-20050927-C: + * Encoded-string-value = Text-string | Value-length Char-set Text-string + */ + assert(enStr != null); + + int charset = enStr.getCharacterSet(); + byte[] textString = enStr.getTextString(); + if (null == textString) { + return; + } + + /* + * In the implementation of EncodedStringValue, the charset field will + * never be 0. It will always be composed as + * Encoded-string-value = Value-length Char-set Text-string + */ + mStack.newbuf(); + PositionMarker start = mStack.mark(); + + appendShortInteger(charset); + appendTextString(textString); + + int len = start.getLength(); + mStack.pop(); + appendValueLength(len); + mStack.copy(); + } + + /** + * Append uintvar integer into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendUintvarInteger(long value) { + /* + * From WAP-230-WSP-20010705-a: + * To encode a large unsigned integer, split it into 7-bit fragments + * and place them in the payloads of multiple octets. The most significant + * bits are placed in the first octets with the least significant bits + * ending up in the last octet. All octets MUST set the Continue bit to 1 + * except the last octet, which MUST set the Continue bit to 0. + */ + int i; + long max = SHORT_INTEGER_MAX; + + for (i = 0; i < 5; i++) { + if (value < max) { + break; + } + + max = (max << 7) | 0x7fl; + } + + while(i > 0) { + long temp = value >>> (i * 7); + temp = temp & 0x7f; + + append((int)((temp | 0x80) & 0xff)); + + i--; + } + + append((int)(value & 0x7f)); + } + + /** + * Append date value into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendDateValue(long date) { + /* + * From OMA-TS-MMS-ENC-V1_3-20050927-C: + * Date-value = Long-integer + */ + appendLongInteger(date); + } + + /** + * Append value length to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendValueLength(long value) { + /* + * From WAP-230-WSP-20010705-a: + * Value-length = Short-length | (Length-quote Length) + * ; Value length is used to indicate the length of the value to follow + * Short-length = + * Length-quote = + * Length = Uintvar-integer + */ + if (value < LENGTH_QUOTE) { + appendShortLength((int) value); + return; + } + + append(LENGTH_QUOTE); + appendUintvarInteger(value); + } + + /** + * Append quoted string to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendQuotedString(byte[] text) { + /* + * From WAP-230-WSP-20010705-a: + * Quoted-string = *TEXT End-of-string + * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing + * ;quotation-marks <"> removed. + */ + append(QUOTED_STRING_FLAG); + arraycopy(text, 0, text.length); + append(END_STRING_FLAG); + } + + /** + * Append quoted string to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + @UnsupportedAppUsage + protected void appendQuotedString(String str) { + /* + * From WAP-230-WSP-20010705-a: + * Quoted-string = *TEXT End-of-string + * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing + * ;quotation-marks <"> removed. + */ + appendQuotedString(str.getBytes()); + } + + private EncodedStringValue appendAddressType(EncodedStringValue address) { + EncodedStringValue temp = null; + + try { + int addressType = checkAddressType(address.getString()); + temp = EncodedStringValue.copy(address); + if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) { + // Phone number. + temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes()); + } else if (PDU_IPV4_ADDRESS_TYPE == addressType) { + // Ipv4 address. + temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes()); + } else if (PDU_IPV6_ADDRESS_TYPE == addressType) { + // Ipv6 address. + temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes()); + } + } catch (NullPointerException e) { + return null; + } + + return temp; + } + + /** + * Append header to mMessage. + */ + @UnsupportedAppUsage + private int appendHeader(int field) { + switch (field) { + case PduHeaders.MMS_VERSION: + appendOctet(field); + + int version = mPduHeader.getOctet(field); + if (0 == version) { + appendShortInteger(PduHeaders.CURRENT_MMS_VERSION); + } else { + appendShortInteger(version); + } + + break; + + case PduHeaders.MESSAGE_ID: + case PduHeaders.TRANSACTION_ID: + byte[] textString = mPduHeader.getTextString(field); + if (null == textString) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendTextString(textString); + break; + + case PduHeaders.TO: + case PduHeaders.BCC: + case PduHeaders.CC: + EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field); + + if (null == addr) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + EncodedStringValue temp; + for (int i = 0; i < addr.length; i++) { + temp = appendAddressType(addr[i]); + if (temp == null) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + appendOctet(field); + appendEncodedString(temp); + } + break; + + case PduHeaders.FROM: + // Value-length (Address-present-token Encoded-string-value | Insert-address-token) + appendOctet(field); + + EncodedStringValue from = mPduHeader.getEncodedStringValue(field); + if ((from == null) + || TextUtils.isEmpty(from.getString()) + || new String(from.getTextString()).equals( + PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) { + // Length of from = 1 + append(1); + // Insert-address-token = + append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN); + } else { + mStack.newbuf(); + PositionMarker fstart = mStack.mark(); + + // Address-present-token = + append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN); + + temp = appendAddressType(from); + if (temp == null) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + appendEncodedString(temp); + + int flen = fstart.getLength(); + mStack.pop(); + appendValueLength(flen); + mStack.copy(); + } + break; + + case PduHeaders.READ_STATUS: + case PduHeaders.STATUS: + case PduHeaders.REPORT_ALLOWED: + case PduHeaders.PRIORITY: + case PduHeaders.DELIVERY_REPORT: + case PduHeaders.READ_REPORT: + case PduHeaders.RETRIEVE_STATUS: + int octet = mPduHeader.getOctet(field); + if (0 == octet) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendOctet(octet); + break; + + case PduHeaders.DATE: + long date = mPduHeader.getLongInteger(field); + if (-1 == date) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendDateValue(date); + break; + + case PduHeaders.SUBJECT: + case PduHeaders.RETRIEVE_TEXT: + EncodedStringValue enString = + mPduHeader.getEncodedStringValue(field); + if (null == enString) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendEncodedString(enString); + break; + + case PduHeaders.MESSAGE_CLASS: + byte[] messageClass = mPduHeader.getTextString(field); + if (null == messageClass) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT); + } else if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_AUTO); + } else if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL); + } else if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL); + } else { + appendTextString(messageClass); + } + break; + + case PduHeaders.EXPIRY: + long expiry = mPduHeader.getLongInteger(field); + if (-1 == expiry) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + + mStack.newbuf(); + PositionMarker expiryStart = mStack.mark(); + + append(PduHeaders.VALUE_RELATIVE_TOKEN); + appendLongInteger(expiry); + + int expiryLength = expiryStart.getLength(); + mStack.pop(); + appendValueLength(expiryLength); + mStack.copy(); + break; + + default: + return PDU_COMPOSE_FIELD_NOT_SUPPORTED; + } + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make ReadRec.Ind. + */ + private int makeReadRecInd() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND); + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Message-ID + if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // To + if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // From + if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Date Optional + appendHeader(PduHeaders.DATE); + + // X-Mms-Read-Status + if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Applic-ID Optional(not support) + // X-Mms-Reply-Applic-ID Optional(not support) + // X-Mms-Aux-Applic-Info Optional(not support) + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make NotifyResp.Ind. + */ + private int makeNotifyResp() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); + + // X-Mms-Transaction-ID + if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Status + if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Report-Allowed Optional (not support) + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make Acknowledge.Ind. + */ + private int makeAckInd() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); + + // X-Mms-Transaction-ID + if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Report-Allowed Optional + appendHeader(PduHeaders.REPORT_ALLOWED); + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make Send.req. + */ + private int makeSendRetrievePdu(int type) { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(type); + + // X-Mms-Transaction-ID + appendOctet(PduHeaders.TRANSACTION_ID); + + byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID); + if (trid == null) { + // Transaction-ID should be set(by Transaction) before make(). + throw new IllegalArgumentException("Transaction-ID is null."); + } + appendTextString(trid); + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Date Date-value Optional. + appendHeader(PduHeaders.DATE); + + // From + if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + boolean recipient = false; + + // To + if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) { + recipient = true; + } + + // Cc + if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) { + recipient = true; + } + + // Bcc + if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) { + recipient = true; + } + + // Need at least one of "cc", "bcc" and "to". + if (false == recipient) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Subject Optional + appendHeader(PduHeaders.SUBJECT); + + // X-Mms-Message-Class Optional + // Message-class-value = Class-identifier | Token-text + appendHeader(PduHeaders.MESSAGE_CLASS); + + // X-Mms-Expiry Optional + appendHeader(PduHeaders.EXPIRY); + + // X-Mms-Priority Optional + appendHeader(PduHeaders.PRIORITY); + + // X-Mms-Delivery-Report Optional + appendHeader(PduHeaders.DELIVERY_REPORT); + + // X-Mms-Read-Report Optional + appendHeader(PduHeaders.READ_REPORT); + + if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) { + // X-Mms-Retrieve-Status Optional + appendHeader(PduHeaders.RETRIEVE_STATUS); + // X-Mms-Retrieve-Text Optional + appendHeader(PduHeaders.RETRIEVE_TEXT); + } + + + // Content-Type + appendOctet(PduHeaders.CONTENT_TYPE); + + // Message body + return makeMessageBody(type); + } + + /** + * Make message body. + */ + private int makeMessageBody(int type) { + // 1. add body informations + mStack.newbuf(); // Switching buffer because we need to + + PositionMarker ctStart = mStack.mark(); + + // This contentTypeIdentifier should be used for type of attachment... + String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE)); + Integer contentTypeIdentifier = mContentTypeMap.get(contentType); + if (contentTypeIdentifier == null) { + // content type is mandatory + return PDU_COMPOSE_CONTENT_ERROR; + } + + appendShortInteger(contentTypeIdentifier.intValue()); + + // content-type parameter: start + PduBody body; + if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) { + body = ((RetrieveConf) mPdu).getBody(); + } else { + body = ((SendReq) mPdu).getBody(); + } + if (null == body || body.getPartsNum() == 0) { + // empty message + appendUintvarInteger(0); + mStack.pop(); + mStack.copy(); + return PDU_COMPOSE_SUCCESS; + } + + PduPart part; + try { + part = body.getPart(0); + + byte[] start = part.getContentId(); + if (start != null) { + appendOctet(PduPart.P_DEP_START); + if (('<' == start[0]) && ('>' == start[start.length - 1])) { + appendTextString(start); + } else { + appendTextString("<" + new String(start) + ">"); + } + } + + // content-type parameter: type + appendOctet(PduPart.P_CT_MR_TYPE); + appendTextString(part.getContentType()); + } + catch (ArrayIndexOutOfBoundsException e){ + e.printStackTrace(); + } + + int ctLength = ctStart.getLength(); + mStack.pop(); + appendValueLength(ctLength); + mStack.copy(); + + // 3. add content + int partNum = body.getPartsNum(); + appendUintvarInteger(partNum); + for (int i = 0; i < partNum; i++) { + part = body.getPart(i); + mStack.newbuf(); // Leaving space for header lengh and data length + PositionMarker attachment = mStack.mark(); + + mStack.newbuf(); // Leaving space for Content-Type length + PositionMarker contentTypeBegin = mStack.mark(); + + byte[] partContentType = part.getContentType(); + + if (partContentType == null) { + // content type is mandatory + return PDU_COMPOSE_CONTENT_ERROR; + } + + // content-type value + Integer partContentTypeIdentifier = + mContentTypeMap.get(new String(partContentType)); + if (partContentTypeIdentifier == null) { + appendTextString(partContentType); + } else { + appendShortInteger(partContentTypeIdentifier.intValue()); + } + + /* Content-type parameter : name. + * The value of name, filename, content-location is the same. + * Just one of them is enough for this PDU. + */ + byte[] name = part.getName(); + + if (null == name) { + name = part.getFilename(); + + if (null == name) { + name = part.getContentLocation(); + + if (null == name) { + /* at lease one of name, filename, Content-location + * should be available. + */ + return PDU_COMPOSE_CONTENT_ERROR; + } + } + } + appendOctet(PduPart.P_DEP_NAME); + appendTextString(name); + + // content-type parameter : charset + int charset = part.getCharset(); + if (charset != 0) { + appendOctet(PduPart.P_CHARSET); + appendShortInteger(charset); + } + + int contentTypeLength = contentTypeBegin.getLength(); + mStack.pop(); + appendValueLength(contentTypeLength); + mStack.copy(); + + // content id + byte[] contentId = part.getContentId(); + + if (null != contentId) { + appendOctet(PduPart.P_CONTENT_ID); + if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) { + appendQuotedString(contentId); + } else { + appendQuotedString("<" + new String(contentId) + ">"); + } + } + + // content-location + byte[] contentLocation = part.getContentLocation(); + if (null != contentLocation) { + appendOctet(PduPart.P_CONTENT_LOCATION); + appendTextString(contentLocation); + } + + // content + int headerLength = attachment.getLength(); + + int dataLength = 0; // Just for safety... + byte[] partData = part.getData(); + + if (partData != null) { + arraycopy(partData, 0, partData.length); + dataLength = partData.length; + } else { + InputStream cr = null; + try { + byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE]; + cr = mResolver.openInputStream(part.getDataUri()); + int len = 0; + while ((len = cr.read(buffer)) != -1) { + mMessage.write(buffer, 0, len); + mPosition += len; + dataLength += len; + } + } catch (FileNotFoundException e) { + return PDU_COMPOSE_CONTENT_ERROR; + } catch (IOException e) { + return PDU_COMPOSE_CONTENT_ERROR; + } catch (RuntimeException e) { + return PDU_COMPOSE_CONTENT_ERROR; + } finally { + if (cr != null) { + try { + cr.close(); + } catch (IOException e) { + } + } + } + } + + if (dataLength != (attachment.getLength() - headerLength)) { + throw new RuntimeException("BUG: Length sanity check failed"); + } + + mStack.pop(); + appendUintvarInteger(headerLength); + appendUintvarInteger(dataLength); + mStack.copy(); + } + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Record current message informations. + */ + static private class LengthRecordNode { + ByteArrayOutputStream currentMessage = null; + public int currentPosition = 0; + + public LengthRecordNode next = null; + } + + /** + * Mark current message position and stact size. + */ + private class PositionMarker { + private int c_pos; // Current position + private int currentStackSize; // Current stack size + + @UnsupportedAppUsage + int getLength() { + // If these assert fails, likely that you are finding the + // size of buffer that is deep in BufferStack you can only + // find the length of the buffer that is on top + if (currentStackSize != mStack.stackSize) { + throw new RuntimeException("BUG: Invalid call to getLength()"); + } + + return mPosition - c_pos; + } + } + + /** + * This implementation can be OPTIMIZED to use only + * 2 buffers. This optimization involves changing BufferStack + * only... Its usage (interface) will not change. + */ + private class BufferStack { + private LengthRecordNode stack = null; + private LengthRecordNode toCopy = null; + + int stackSize = 0; + + /** + * Create a new message buffer and push it into the stack. + */ + @UnsupportedAppUsage + void newbuf() { + // You can't create a new buff when toCopy != null + // That is after calling pop() and before calling copy() + // If you do, it is a bug + if (toCopy != null) { + throw new RuntimeException("BUG: Invalid newbuf() before copy()"); + } + + LengthRecordNode temp = new LengthRecordNode(); + + temp.currentMessage = mMessage; + temp.currentPosition = mPosition; + + temp.next = stack; + stack = temp; + + stackSize = stackSize + 1; + + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + /** + * Pop the message before and record current message in the stack. + */ + @UnsupportedAppUsage + void pop() { + ByteArrayOutputStream currentMessage = mMessage; + int currentPosition = mPosition; + + mMessage = stack.currentMessage; + mPosition = stack.currentPosition; + + toCopy = stack; + // Re using the top element of the stack to avoid memory allocation + + stack = stack.next; + stackSize = stackSize - 1; + + toCopy.currentMessage = currentMessage; + toCopy.currentPosition = currentPosition; + } + + /** + * Append current message to the message before. + */ + @UnsupportedAppUsage + void copy() { + arraycopy(toCopy.currentMessage.toByteArray(), 0, + toCopy.currentPosition); + + toCopy = null; + } + + /** + * Mark current message position + */ + @UnsupportedAppUsage + PositionMarker mark() { + PositionMarker m = new PositionMarker(); + + m.c_pos = mPosition; + m.currentStackSize = stackSize; + + return m; + } + } + + /** + * Check address type. + * + * @param address address string without the postfix stinng type, + * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4" + * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number, + * PDU_EMAIL_ADDRESS_TYPE if it is email address, + * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address, + * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address, + * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown. + */ + protected static int checkAddressType(String address) { + /** + * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8. + * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode) + * e-mail = mailbox; to the definition of mailbox as described in + * section 3.4 of [RFC2822], but excluding the + * obsolete definitions as indicated by the "obs-" prefix. + * device-address = ( global-phone-number "/TYPE=PLMN" ) + * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" ) + * / ( escaped-value "/TYPE=" address-type ) + * + * global-phone-number = ["+"] 1*( DIGIT / written-sep ) + * written-sep =("-"/".") + * + * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value + * + * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373 + */ + + if (null == address) { + return PDU_UNKNOWN_ADDRESS_TYPE; + } + + if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) { + // Ipv4 address. + return PDU_IPV4_ADDRESS_TYPE; + }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) { + // Phone number. + return PDU_PHONE_NUMBER_ADDRESS_TYPE; + } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) { + // Email address. + return PDU_EMAIL_ADDRESS_TYPE; + } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) { + // Ipv6 address. + return PDU_IPV6_ADDRESS_TYPE; + } else { + // Unknown address. + return PDU_UNKNOWN_ADDRESS_TYPE; + } + } +} diff --git a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java new file mode 100644 index 000000000000..8551b2f9b693 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +public class PduContentTypes { + /** + * All content types. From: + * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm + */ + @UnsupportedAppUsage + static final String[] contentTypes = { + "*/*", /* 0x00 */ + "text/*", /* 0x01 */ + "text/html", /* 0x02 */ + "text/plain", /* 0x03 */ + "text/x-hdml", /* 0x04 */ + "text/x-ttml", /* 0x05 */ + "text/x-vCalendar", /* 0x06 */ + "text/x-vCard", /* 0x07 */ + "text/vnd.wap.wml", /* 0x08 */ + "text/vnd.wap.wmlscript", /* 0x09 */ + "text/vnd.wap.wta-event", /* 0x0A */ + "multipart/*", /* 0x0B */ + "multipart/mixed", /* 0x0C */ + "multipart/form-data", /* 0x0D */ + "multipart/byterantes", /* 0x0E */ + "multipart/alternative", /* 0x0F */ + "application/*", /* 0x10 */ + "application/java-vm", /* 0x11 */ + "application/x-www-form-urlencoded", /* 0x12 */ + "application/x-hdmlc", /* 0x13 */ + "application/vnd.wap.wmlc", /* 0x14 */ + "application/vnd.wap.wmlscriptc", /* 0x15 */ + "application/vnd.wap.wta-eventc", /* 0x16 */ + "application/vnd.wap.uaprof", /* 0x17 */ + "application/vnd.wap.wtls-ca-certificate", /* 0x18 */ + "application/vnd.wap.wtls-user-certificate", /* 0x19 */ + "application/x-x509-ca-cert", /* 0x1A */ + "application/x-x509-user-cert", /* 0x1B */ + "image/*", /* 0x1C */ + "image/gif", /* 0x1D */ + "image/jpeg", /* 0x1E */ + "image/tiff", /* 0x1F */ + "image/png", /* 0x20 */ + "image/vnd.wap.wbmp", /* 0x21 */ + "application/vnd.wap.multipart.*", /* 0x22 */ + "application/vnd.wap.multipart.mixed", /* 0x23 */ + "application/vnd.wap.multipart.form-data", /* 0x24 */ + "application/vnd.wap.multipart.byteranges", /* 0x25 */ + "application/vnd.wap.multipart.alternative", /* 0x26 */ + "application/xml", /* 0x27 */ + "text/xml", /* 0x28 */ + "application/vnd.wap.wbxml", /* 0x29 */ + "application/x-x968-cross-cert", /* 0x2A */ + "application/x-x968-ca-cert", /* 0x2B */ + "application/x-x968-user-cert", /* 0x2C */ + "text/vnd.wap.si", /* 0x2D */ + "application/vnd.wap.sic", /* 0x2E */ + "text/vnd.wap.sl", /* 0x2F */ + "application/vnd.wap.slc", /* 0x30 */ + "text/vnd.wap.co", /* 0x31 */ + "application/vnd.wap.coc", /* 0x32 */ + "application/vnd.wap.multipart.related", /* 0x33 */ + "application/vnd.wap.sia", /* 0x34 */ + "text/vnd.wap.connectivity-xml", /* 0x35 */ + "application/vnd.wap.connectivity-wbxml", /* 0x36 */ + "application/pkcs7-mime", /* 0x37 */ + "application/vnd.wap.hashed-certificate", /* 0x38 */ + "application/vnd.wap.signed-certificate", /* 0x39 */ + "application/vnd.wap.cert-response", /* 0x3A */ + "application/xhtml+xml", /* 0x3B */ + "application/wml+xml", /* 0x3C */ + "text/css", /* 0x3D */ + "application/vnd.wap.mms-message", /* 0x3E */ + "application/vnd.wap.rollover-certificate", /* 0x3F */ + "application/vnd.wap.locc+wbxml", /* 0x40 */ + "application/vnd.wap.loc+xml", /* 0x41 */ + "application/vnd.syncml.dm+wbxml", /* 0x42 */ + "application/vnd.syncml.dm+xml", /* 0x43 */ + "application/vnd.syncml.notification", /* 0x44 */ + "application/vnd.wap.xhtml+xml", /* 0x45 */ + "application/vnd.wv.csp.cir", /* 0x46 */ + "application/vnd.oma.dd+xml", /* 0x47 */ + "application/vnd.oma.drm.message", /* 0x48 */ + "application/vnd.oma.drm.content", /* 0x49 */ + "application/vnd.oma.drm.rights+xml", /* 0x4A */ + "application/vnd.oma.drm.rights+wbxml", /* 0x4B */ + "application/vnd.wv.csp+xml", /* 0x4C */ + "application/vnd.wv.csp+wbxml", /* 0x4D */ + "application/vnd.syncml.ds.notification", /* 0x4E */ + "audio/*", /* 0x4F */ + "video/*", /* 0x50 */ + "application/vnd.oma.dd2+xml", /* 0x51 */ + "application/mikey" /* 0x52 */ + }; +} diff --git a/telephony/common/com/google/android/mms/pdu/PduHeaders.java b/telephony/common/com/google/android/mms/pdu/PduHeaders.java new file mode 100644 index 000000000000..b5244645fda1 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduHeaders.java @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +import java.util.ArrayList; +import java.util.HashMap; + +public class PduHeaders { + /** + * All pdu header fields. + */ + public static final int BCC = 0x81; + public static final int CC = 0x82; + public static final int CONTENT_LOCATION = 0x83; + public static final int CONTENT_TYPE = 0x84; + public static final int DATE = 0x85; + public static final int DELIVERY_REPORT = 0x86; + public static final int DELIVERY_TIME = 0x87; + public static final int EXPIRY = 0x88; + public static final int FROM = 0x89; + public static final int MESSAGE_CLASS = 0x8A; + public static final int MESSAGE_ID = 0x8B; + public static final int MESSAGE_TYPE = 0x8C; + public static final int MMS_VERSION = 0x8D; + public static final int MESSAGE_SIZE = 0x8E; + public static final int PRIORITY = 0x8F; + + public static final int READ_REPLY = 0x90; + public static final int READ_REPORT = 0x90; + public static final int REPORT_ALLOWED = 0x91; + public static final int RESPONSE_STATUS = 0x92; + public static final int RESPONSE_TEXT = 0x93; + public static final int SENDER_VISIBILITY = 0x94; + public static final int STATUS = 0x95; + public static final int SUBJECT = 0x96; + public static final int TO = 0x97; + public static final int TRANSACTION_ID = 0x98; + public static final int RETRIEVE_STATUS = 0x99; + public static final int RETRIEVE_TEXT = 0x9A; + public static final int READ_STATUS = 0x9B; + public static final int REPLY_CHARGING = 0x9C; + public static final int REPLY_CHARGING_DEADLINE = 0x9D; + public static final int REPLY_CHARGING_ID = 0x9E; + public static final int REPLY_CHARGING_SIZE = 0x9F; + + public static final int PREVIOUSLY_SENT_BY = 0xA0; + public static final int PREVIOUSLY_SENT_DATE = 0xA1; + public static final int STORE = 0xA2; + public static final int MM_STATE = 0xA3; + public static final int MM_FLAGS = 0xA4; + public static final int STORE_STATUS = 0xA5; + public static final int STORE_STATUS_TEXT = 0xA6; + public static final int STORED = 0xA7; + public static final int ATTRIBUTES = 0xA8; + public static final int TOTALS = 0xA9; + public static final int MBOX_TOTALS = 0xAA; + public static final int QUOTAS = 0xAB; + public static final int MBOX_QUOTAS = 0xAC; + public static final int MESSAGE_COUNT = 0xAD; + public static final int CONTENT = 0xAE; + public static final int START = 0xAF; + + public static final int ADDITIONAL_HEADERS = 0xB0; + public static final int DISTRIBUTION_INDICATOR = 0xB1; + public static final int ELEMENT_DESCRIPTOR = 0xB2; + public static final int LIMIT = 0xB3; + public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4; + public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5; + public static final int STATUS_TEXT = 0xB6; + public static final int APPLIC_ID = 0xB7; + public static final int REPLY_APPLIC_ID = 0xB8; + public static final int AUX_APPLIC_ID = 0xB9; + public static final int CONTENT_CLASS = 0xBA; + public static final int DRM_CONTENT = 0xBB; + public static final int ADAPTATION_ALLOWED = 0xBC; + public static final int REPLACE_ID = 0xBD; + public static final int CANCEL_ID = 0xBE; + public static final int CANCEL_STATUS = 0xBF; + + /** + * X-Mms-Message-Type field types. + */ + public static final int MESSAGE_TYPE_SEND_REQ = 0x80; + public static final int MESSAGE_TYPE_SEND_CONF = 0x81; + public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82; + public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83; + public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; + public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85; + public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86; + public static final int MESSAGE_TYPE_READ_REC_IND = 0x87; + public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88; + public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89; + public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A; + public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B; + public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C; + public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D; + public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E; + public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F; + public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90; + public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91; + public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92; + public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93; + public static final int MESSAGE_TYPE_DELETE_REQ = 0x94; + public static final int MESSAGE_TYPE_DELETE_CONF = 0x95; + public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96; + public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97; + + /** + * X-Mms-Delivery-Report | + * X-Mms-Read-Report | + * X-Mms-Report-Allowed | + * X-Mms-Sender-Visibility | + * X-Mms-Store | + * X-Mms-Stored | + * X-Mms-Totals | + * X-Mms-Quotas | + * X-Mms-Distribution-Indicator | + * X-Mms-DRM-Content | + * X-Mms-Adaptation-Allowed | + * field types. + */ + public static final int VALUE_YES = 0x80; + public static final int VALUE_NO = 0x81; + + /** + * Delivery-Time | + * Expiry and Reply-Charging-Deadline | + * field type components. + */ + public static final int VALUE_ABSOLUTE_TOKEN = 0x80; + public static final int VALUE_RELATIVE_TOKEN = 0x81; + + /** + * X-Mms-MMS-Version field types. + */ + public static final int MMS_VERSION_1_3 = ((1 << 4) | 3); + public static final int MMS_VERSION_1_2 = ((1 << 4) | 2); + public static final int MMS_VERSION_1_1 = ((1 << 4) | 1); + public static final int MMS_VERSION_1_0 = ((1 << 4) | 0); + + // Current version is 1.2. + public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2; + + /** + * From field type components. + */ + public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80; + public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81; + + public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token"; + public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token"; + + /** + * X-Mms-Status Field. + */ + public static final int STATUS_EXPIRED = 0x80; + public static final int STATUS_RETRIEVED = 0x81; + public static final int STATUS_REJECTED = 0x82; + public static final int STATUS_DEFERRED = 0x83; + public static final int STATUS_UNRECOGNIZED = 0x84; + public static final int STATUS_INDETERMINATE = 0x85; + public static final int STATUS_FORWARDED = 0x86; + public static final int STATUS_UNREACHABLE = 0x87; + + /** + * MM-Flags field type components. + */ + public static final int MM_FLAGS_ADD_TOKEN = 0x80; + public static final int MM_FLAGS_REMOVE_TOKEN = 0x81; + public static final int MM_FLAGS_FILTER_TOKEN = 0x82; + + /** + * X-Mms-Message-Class field types. + */ + public static final int MESSAGE_CLASS_PERSONAL = 0x80; + public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81; + public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82; + public static final int MESSAGE_CLASS_AUTO = 0x83; + + public static final String MESSAGE_CLASS_PERSONAL_STR = "personal"; + public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement"; + public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational"; + public static final String MESSAGE_CLASS_AUTO_STR = "auto"; + + /** + * X-Mms-Priority field types. + */ + public static final int PRIORITY_LOW = 0x80; + public static final int PRIORITY_NORMAL = 0x81; + public static final int PRIORITY_HIGH = 0x82; + + /** + * X-Mms-Response-Status field types. + */ + public static final int RESPONSE_STATUS_OK = 0x80; + public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81; + public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82; + + public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83; + public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84; + + public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85; + public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86; + public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87; + public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; + + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4; + + public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED = 0xE3; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE4; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED = 0xE5; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET = 0xE6; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED = 0xE8; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED = 0xE9; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED = 0xEA; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID = 0xEB; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_END = 0xFF; + + /** + * X-Mms-Retrieve-Status field types. + */ + public static final int RETRIEVE_STATUS_OK = 0x80; + public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; + public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1; + public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3; + public static final int RETRIEVE_STATUS_ERROR_END = 0xFF; + + /** + * X-Mms-Sender-Visibility field types. + */ + public static final int SENDER_VISIBILITY_HIDE = 0x80; + public static final int SENDER_VISIBILITY_SHOW = 0x81; + + /** + * X-Mms-Read-Status field types. + */ + public static final int READ_STATUS_READ = 0x80; + public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81; + + /** + * X-Mms-Cancel-Status field types. + */ + public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80; + public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81; + + /** + * X-Mms-Reply-Charging field types. + */ + public static final int REPLY_CHARGING_REQUESTED = 0x80; + public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81; + public static final int REPLY_CHARGING_ACCEPTED = 0x82; + public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83; + + /** + * X-Mms-MM-State field types. + */ + public static final int MM_STATE_DRAFT = 0x80; + public static final int MM_STATE_SENT = 0x81; + public static final int MM_STATE_NEW = 0x82; + public static final int MM_STATE_RETRIEVED = 0x83; + public static final int MM_STATE_FORWARDED = 0x84; + + /** + * X-Mms-Recommended-Retrieval-Mode field types. + */ + public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80; + + /** + * X-Mms-Content-Class field types. + */ + public static final int CONTENT_CLASS_TEXT = 0x80; + public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81; + public static final int CONTENT_CLASS_IMAGE_RICH = 0x82; + public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83; + public static final int CONTENT_CLASS_VIDEO_RICH = 0x84; + public static final int CONTENT_CLASS_MEGAPIXEL = 0x85; + public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86; + public static final int CONTENT_CLASS_CONTENT_RICH = 0x87; + + /** + * X-Mms-Store-Status field types. + */ + public static final int STORE_STATUS_SUCCESS = 0x80; + public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; + public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1; + public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; + public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; + public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; + public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3; + public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4; + public static final int STORE_STATUS_ERROR_END = 0xFF; + + /** + * The map contains the value of all headers. + */ + private HashMap mHeaderMap = null; + + /** + * Constructor of PduHeaders. + */ + @UnsupportedAppUsage + public PduHeaders() { + mHeaderMap = new HashMap(); + } + + /** + * Get octet value by header field. + * + * @param field the field + * @return the octet value of the pdu header + * with specified header field. Return 0 if + * the value is not set. + */ + @UnsupportedAppUsage + protected int getOctet(int field) { + Integer octet = (Integer) mHeaderMap.get(field); + if (null == octet) { + return 0; + } + + return octet; + } + + /** + * Set octet value to pdu header by header field. + * + * @param value the value + * @param field the field + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + protected void setOctet(int value, int field) + throws InvalidHeaderValueException{ + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + switch (field) { + case REPORT_ALLOWED: + case ADAPTATION_ALLOWED: + case DELIVERY_REPORT: + case DRM_CONTENT: + case DISTRIBUTION_INDICATOR: + case QUOTAS: + case READ_REPORT: + case STORE: + case STORED: + case TOTALS: + case SENDER_VISIBILITY: + if ((VALUE_YES != value) && (VALUE_NO != value)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case READ_STATUS: + if ((READ_STATUS_READ != value) && + (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case CANCEL_STATUS: + if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) && + (CANCEL_STATUS_REQUEST_CORRUPTED != value)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case PRIORITY: + if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case STATUS: + if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case REPLY_CHARGING: + if ((value < REPLY_CHARGING_REQUESTED) + || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case MM_STATE: + if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case RECOMMENDED_RETRIEVAL_MODE: + if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case CONTENT_CLASS: + if ((value < CONTENT_CLASS_TEXT) + || (value > CONTENT_CLASS_CONTENT_RICH)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case RETRIEVE_STATUS: + // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value. + if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && + (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) { + value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE; + } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) && + (value <= RETRIEVE_STATUS_ERROR_END)) { + value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; + } else if ((value < RETRIEVE_STATUS_OK) || + ((value > RETRIEVE_STATUS_OK) && + (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) || + (value > RETRIEVE_STATUS_ERROR_END)) { + value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; + } + break; + case STORE_STATUS: + // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value. + if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && + (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) { + value = STORE_STATUS_ERROR_TRANSIENT_FAILURE; + } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) && + (value <= STORE_STATUS_ERROR_END)) { + value = STORE_STATUS_ERROR_PERMANENT_FAILURE; + } else if ((value < STORE_STATUS_SUCCESS) || + ((value > STORE_STATUS_SUCCESS) && + (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) || + (value > STORE_STATUS_ERROR_END)) { + value = STORE_STATUS_ERROR_PERMANENT_FAILURE; + } + break; + case RESPONSE_STATUS: + // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value. + if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) && + (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) { + value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE; + } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) && + (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) || + (value < RESPONSE_STATUS_OK) || + ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) && + (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) || + (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) { + value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE; + } + break; + case MMS_VERSION: + if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) { + value = CURRENT_MMS_VERSION; // Current version is the default value. + } + break; + case MESSAGE_TYPE: + if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + default: + // This header value should not be Octect. + throw new RuntimeException("Invalid header field!"); + } + mHeaderMap.put(field, value); + } + + /** + * Get TextString value by header field. + * + * @param field the field + * @return the TextString value of the pdu header + * with specified header field + */ + @UnsupportedAppUsage + protected byte[] getTextString(int field) { + return (byte[]) mHeaderMap.get(field); + } + + /** + * Set TextString value to pdu header by header field. + * + * @param value the value + * @param field the field + * @return the TextString value of the pdu header + * with specified header field + * @throws NullPointerException if the value is null. + */ + protected void setTextString(byte[] value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case TRANSACTION_ID: + case REPLY_CHARGING_ID: + case AUX_APPLIC_ID: + case APPLIC_ID: + case REPLY_APPLIC_ID: + case MESSAGE_ID: + case REPLACE_ID: + case CANCEL_ID: + case CONTENT_LOCATION: + case MESSAGE_CLASS: + case CONTENT_TYPE: + break; + default: + // This header value should not be Text-String. + throw new RuntimeException("Invalid header field!"); + } + mHeaderMap.put(field, value); + } + + /** + * Get EncodedStringValue value by header field. + * + * @param field the field + * @return the EncodedStringValue value of the pdu header + * with specified header field + */ + @UnsupportedAppUsage + protected EncodedStringValue getEncodedStringValue(int field) { + return (EncodedStringValue) mHeaderMap.get(field); + } + + /** + * Get TO, CC or BCC header value. + * + * @param field the field + * @return the EncodeStringValue array of the pdu header + * with specified header field + */ + @UnsupportedAppUsage + protected EncodedStringValue[] getEncodedStringValues(int field) { + ArrayList list = + (ArrayList) mHeaderMap.get(field); + if (null == list) { + return null; + } + EncodedStringValue[] values = new EncodedStringValue[list.size()]; + return list.toArray(values); + } + + /** + * Set EncodedStringValue value to pdu header by header field. + * + * @param value the value + * @param field the field + * @return the EncodedStringValue value of the pdu header + * with specified header field + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + protected void setEncodedStringValue(EncodedStringValue value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case SUBJECT: + case RECOMMENDED_RETRIEVAL_MODE_TEXT: + case RETRIEVE_TEXT: + case STATUS_TEXT: + case STORE_STATUS_TEXT: + case RESPONSE_TEXT: + case FROM: + case PREVIOUSLY_SENT_BY: + case MM_FLAGS: + break; + default: + // This header value should not be Encoded-String-Value. + throw new RuntimeException("Invalid header field!"); + } + + mHeaderMap.put(field, value); + } + + /** + * Set TO, CC or BCC header value. + * + * @param value the value + * @param field the field + * @return the EncodedStringValue value array of the pdu header + * with specified header field + * @throws NullPointerException if the value is null. + */ + protected void setEncodedStringValues(EncodedStringValue[] value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case BCC: + case CC: + case TO: + break; + default: + // This header value should not be Encoded-String-Value. + throw new RuntimeException("Invalid header field!"); + } + + ArrayList list = new ArrayList(); + for (int i = 0; i < value.length; i++) { + list.add(value[i]); + } + mHeaderMap.put(field, list); + } + + /** + * Append one EncodedStringValue to another. + * + * @param value the EncodedStringValue to append + * @param field the field + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + protected void appendEncodedStringValue(EncodedStringValue value, + int field) { + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case BCC: + case CC: + case TO: + break; + default: + throw new RuntimeException("Invalid header field!"); + } + + ArrayList list = + (ArrayList) mHeaderMap.get(field); + if (null == list) { + list = new ArrayList(); + } + list.add(value); + mHeaderMap.put(field, list); + } + + /** + * Get LongInteger value by header field. + * + * @param field the field + * @return the LongInteger value of the pdu header + * with specified header field. if return -1, the + * field is not existed in pdu header. + */ + @UnsupportedAppUsage + protected long getLongInteger(int field) { + Long longInteger = (Long) mHeaderMap.get(field); + if (null == longInteger) { + return -1; + } + + return longInteger.longValue(); + } + + /** + * Set LongInteger value to pdu header by header field. + * + * @param value the value + * @param field the field + */ + @UnsupportedAppUsage + protected void setLongInteger(long value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + switch (field) { + case DATE: + case REPLY_CHARGING_SIZE: + case MESSAGE_SIZE: + case MESSAGE_COUNT: + case START: + case LIMIT: + case DELIVERY_TIME: + case EXPIRY: + case REPLY_CHARGING_DEADLINE: + case PREVIOUSLY_SENT_DATE: + break; + default: + // This header value should not be LongInteger. + throw new RuntimeException("Invalid header field!"); + } + mHeaderMap.put(field, value); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/PduParser.java b/telephony/common/com/google/android/mms/pdu/PduParser.java new file mode 100755 index 000000000000..f48399410723 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduParser.java @@ -0,0 +1,2023 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.ContentType; +import com.google.android.mms.InvalidHeaderValueException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.HashMap; + +public class PduParser { + /** + * The next are WAP values defined in WSP specification. + */ + private static final int QUOTE = 127; + private static final int LENGTH_QUOTE = 31; + private static final int TEXT_MIN = 32; + private static final int TEXT_MAX = 127; + private static final int SHORT_INTEGER_MAX = 127; + private static final int SHORT_LENGTH_MAX = 30; + private static final int LONG_INTEGER_LENGTH_MAX = 8; + private static final int QUOTED_STRING_FLAG = 34; + private static final int END_STRING_FLAG = 0x00; + //The next two are used by the interface "parseWapString" to + //distinguish Text-String and Quoted-String. + private static final int TYPE_TEXT_STRING = 0; + private static final int TYPE_QUOTED_STRING = 1; + private static final int TYPE_TOKEN_STRING = 2; + + /** + * Specify the part position. + */ + private static final int THE_FIRST_PART = 0; + private static final int THE_LAST_PART = 1; + + /** + * The pdu data. + */ + private ByteArrayInputStream mPduDataStream = null; + + /** + * Store pdu headers + */ + private PduHeaders mHeaders = null; + + /** + * Store pdu parts. + */ + private PduBody mBody = null; + + /** + * Store the "type" parameter in "Content-Type" header field. + */ + private static byte[] mTypeParam = null; + + /** + * Store the "start" parameter in "Content-Type" header field. + */ + private static byte[] mStartParam = null; + + /** + * The log tag. + */ + private static final String LOG_TAG = "PduParser"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + /** + * Whether to parse content-disposition part header + */ + private final boolean mParseContentDisposition; + + /** + * Constructor. + * + * @param pduDataStream pdu data to be parsed + * @param parseContentDisposition whether to parse the Content-Disposition part header + */ + @UnsupportedAppUsage + public PduParser(byte[] pduDataStream, boolean parseContentDisposition) { + mPduDataStream = new ByteArrayInputStream(pduDataStream); + mParseContentDisposition = parseContentDisposition; + } + + /** + * Parse the pdu. + * + * @return the pdu structure if parsing successfully. + * null if parsing error happened or mandatory fields are not set. + */ + @UnsupportedAppUsage + public GenericPdu parse(){ + if (mPduDataStream == null) { + return null; + } + + /* parse headers */ + mHeaders = parseHeaders(mPduDataStream); + if (null == mHeaders) { + // Parse headers failed. + return null; + } + + /* get the message type */ + int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE); + + /* check mandatory header fields */ + if (false == checkMandatoryHeader(mHeaders)) { + log("check mandatory headers failed!"); + return null; + } + + if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) || + (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) { + /* need to parse the parts */ + mBody = parseParts(mPduDataStream); + if (null == mBody) { + // Parse parts failed. + return null; + } + } + + switch (messageType) { + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ"); + } + SendReq sendReq = new SendReq(mHeaders, mBody); + return sendReq; + case PduHeaders.MESSAGE_TYPE_SEND_CONF: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF"); + } + SendConf sendConf = new SendConf(mHeaders); + return sendConf; + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND"); + } + NotificationInd notificationInd = + new NotificationInd(mHeaders); + return notificationInd; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND"); + } + NotifyRespInd notifyRespInd = + new NotifyRespInd(mHeaders); + return notifyRespInd; + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF"); + } + RetrieveConf retrieveConf = + new RetrieveConf(mHeaders, mBody); + + byte[] contentType = retrieveConf.getContentType(); + if (null == contentType) { + return null; + } + String ctTypeStr = new String(contentType); + if (ctTypeStr.equals(ContentType.MULTIPART_MIXED) + || ctTypeStr.equals(ContentType.MULTIPART_RELATED) + || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) { + // The MMS content type must be "application/vnd.wap.multipart.mixed" + // or "application/vnd.wap.multipart.related" + // or "application/vnd.wap.multipart.alternative" + return retrieveConf; + } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) { + // "application/vnd.wap.multipart.alternative" + // should take only the first part. + PduPart firstPart = mBody.getPart(0); + mBody.removeAll(); + mBody.addPart(0, firstPart); + return retrieveConf; + } + return null; + case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND"); + } + DeliveryInd deliveryInd = + new DeliveryInd(mHeaders); + return deliveryInd; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND"); + } + AcknowledgeInd acknowledgeInd = + new AcknowledgeInd(mHeaders); + return acknowledgeInd; + case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND"); + } + ReadOrigInd readOrigInd = + new ReadOrigInd(mHeaders); + return readOrigInd; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND"); + } + ReadRecInd readRecInd = + new ReadRecInd(mHeaders); + return readRecInd; + default: + log("Parser doesn't support this message type in this version!"); + return null; + } + } + + /** + * Parse pdu headers. + * + * @param pduDataStream pdu data input stream + * @return headers in PduHeaders structure, null when parse fail + */ + protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){ + if (pduDataStream == null) { + return null; + } + boolean keepParsing = true; + PduHeaders headers = new PduHeaders(); + + while (keepParsing && (pduDataStream.available() > 0)) { + pduDataStream.mark(1); + int headerField = extractByteValue(pduDataStream); + /* parse custom text header */ + if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) { + pduDataStream.reset(); + byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); + } + /* we should ignore it at the moment */ + continue; + } + switch (headerField) { + case PduHeaders.MESSAGE_TYPE: + { + int messageType = extractByteValue(pduDataStream); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType); + } + switch (messageType) { + // We don't support these kind of messages now. + case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: + case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: + case PduHeaders.MESSAGE_TYPE_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: + case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: + return null; + } + try { + headers.setOctet(messageType, headerField); + } catch(InvalidHeaderValueException e) { + log("Set invalid Octet value: " + messageType + + " into the header filed: " + headerField); + return null; + } catch(RuntimeException e) { + log(headerField + "is not Octet header field!"); + return null; + } + break; + } + /* Octect value */ + case PduHeaders.REPORT_ALLOWED: + case PduHeaders.ADAPTATION_ALLOWED: + case PduHeaders.DELIVERY_REPORT: + case PduHeaders.DRM_CONTENT: + case PduHeaders.DISTRIBUTION_INDICATOR: + case PduHeaders.QUOTAS: + case PduHeaders.READ_REPORT: + case PduHeaders.STORE: + case PduHeaders.STORED: + case PduHeaders.TOTALS: + case PduHeaders.SENDER_VISIBILITY: + case PduHeaders.READ_STATUS: + case PduHeaders.CANCEL_STATUS: + case PduHeaders.PRIORITY: + case PduHeaders.STATUS: + case PduHeaders.REPLY_CHARGING: + case PduHeaders.MM_STATE: + case PduHeaders.RECOMMENDED_RETRIEVAL_MODE: + case PduHeaders.CONTENT_CLASS: + case PduHeaders.RETRIEVE_STATUS: + case PduHeaders.STORE_STATUS: + /** + * The following field has a different value when + * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. + * For now we ignore this fact, since we do not support these PDUs + */ + case PduHeaders.RESPONSE_STATUS: + { + int value = extractByteValue(pduDataStream); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " + + value); + } + + try { + headers.setOctet(value, headerField); + } catch(InvalidHeaderValueException e) { + log("Set invalid Octet value: " + value + + " into the header filed: " + headerField); + return null; + } catch(RuntimeException e) { + log(headerField + "is not Octet header field!"); + return null; + } + break; + } + + /* Long-Integer */ + case PduHeaders.DATE: + case PduHeaders.REPLY_CHARGING_SIZE: + case PduHeaders.MESSAGE_SIZE: + { + try { + long value = parseLongInteger(pduDataStream); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " + + value); + } + headers.setLongInteger(value, headerField); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + /* Integer-Value */ + case PduHeaders.MESSAGE_COUNT: + case PduHeaders.START: + case PduHeaders.LIMIT: + { + try { + long value = parseIntegerValue(pduDataStream); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " + + value); + } + headers.setLongInteger(value, headerField); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + /* Text-String */ + case PduHeaders.TRANSACTION_ID: + case PduHeaders.REPLY_CHARGING_ID: + case PduHeaders.AUX_APPLIC_ID: + case PduHeaders.APPLIC_ID: + case PduHeaders.REPLY_APPLIC_ID: + /** + * The next three header fields are email addresses + * as defined in RFC2822, + * not including the characters "<" and ">" + */ + case PduHeaders.MESSAGE_ID: + case PduHeaders.REPLACE_ID: + case PduHeaders.CANCEL_ID: + /** + * The following field has a different value when + * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. + * For now we ignore this fact, since we do not support these PDUs + */ + case PduHeaders.CONTENT_LOCATION: + { + byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != value) { + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " + + new String(value)); + } + headers.setTextString(value, headerField); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } + break; + } + + /* Encoded-string-value */ + case PduHeaders.SUBJECT: + case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT: + case PduHeaders.RETRIEVE_TEXT: + case PduHeaders.STATUS_TEXT: + case PduHeaders.STORE_STATUS_TEXT: + /* the next one is not support + * M-Mbox-Delete.conf and M-Delete.conf now */ + case PduHeaders.RESPONSE_TEXT: + { + EncodedStringValue value = + parseEncodedStringValue(pduDataStream); + if (null != value) { + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField + + " value: " + value.getString()); + } + headers.setEncodedStringValue(value, headerField); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch (RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + break; + } + + /* Addressing model */ + case PduHeaders.BCC: + case PduHeaders.CC: + case PduHeaders.TO: + { + EncodedStringValue value = + parseEncodedStringValue(pduDataStream); + if (null != value) { + byte[] address = value.getTextString(); + if (null != address) { + String str = new String(address); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField + + " value: " + str); + } + int endIndex = str.indexOf("/"); + if (endIndex > 0) { + str = str.substring(0, endIndex); + } + try { + value.setTextString(str.getBytes()); + } catch(NullPointerException e) { + log("null pointer error!"); + return null; + } + } + + try { + headers.appendEncodedStringValue(value, headerField); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + break; + } + + /* Value-length + * (Absolute-token Date-value | Relative-token Delta-seconds-value) */ + case PduHeaders.DELIVERY_TIME: + case PduHeaders.EXPIRY: + case PduHeaders.REPLY_CHARGING_DEADLINE: + { + /* parse Value-length */ + parseValueLength(pduDataStream); + + /* Absolute-token or Relative-token */ + int token = extractByteValue(pduDataStream); + + /* Date-value or Delta-seconds-value */ + long timeValue; + try { + timeValue = parseLongInteger(pduDataStream); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + if (PduHeaders.VALUE_RELATIVE_TOKEN == token) { + /* need to convert the Delta-seconds-value + * into Date-value */ + timeValue = System.currentTimeMillis()/1000 + timeValue; + } + + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: time value: " + headerField + + " value: " + timeValue); + } + headers.setLongInteger(timeValue, headerField); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + case PduHeaders.FROM: { + /* From-value = + * Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + */ + EncodedStringValue from = null; + parseValueLength(pduDataStream); /* parse value-length */ + + /* Address-present-token or Insert-address-token */ + int fromToken = extractByteValue(pduDataStream); + + /* Address-present-token or Insert-address-token */ + if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) { + /* Encoded-string-value */ + from = parseEncodedStringValue(pduDataStream); + if (null != from) { + byte[] address = from.getTextString(); + if (null != address) { + String str = new String(address); + int endIndex = str.indexOf("/"); + if (endIndex > 0) { + str = str.substring(0, endIndex); + } + try { + from.setTextString(str.getBytes()); + } catch(NullPointerException e) { + log("null pointer error!"); + return null; + } + } + } + } else { + try { + from = new EncodedStringValue( + PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()); + } catch(NullPointerException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: from address: " + headerField + + " value: " + from.getString()); + } + headers.setEncodedStringValue(from, PduHeaders.FROM); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + break; + } + + case PduHeaders.MESSAGE_CLASS: { + /* Message-class-value = Class-identifier | Token-text */ + pduDataStream.mark(1); + int messageClass = extractByteValue(pduDataStream); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField + + " value: " + messageClass); + } + + if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) { + /* Class-identifier */ + try { + if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } else { + /* Token-text */ + pduDataStream.reset(); + byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != messageClassString) { + try { + headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } + } + break; + } + + case PduHeaders.MMS_VERSION: { + int version = parseShortInteger(pduDataStream); + + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField + + " value: " + version); + } + headers.setOctet(version, PduHeaders.MMS_VERSION); + } catch(InvalidHeaderValueException e) { + log("Set invalid Octet value: " + version + + " into the header filed: " + headerField); + return null; + } catch(RuntimeException e) { + log(headerField + "is not Octet header field!"); + return null; + } + break; + } + + case PduHeaders.PREVIOUSLY_SENT_BY: { + /* Previously-sent-by-value = + * Value-length Forwarded-count-value Encoded-string-value */ + /* parse value-length */ + parseValueLength(pduDataStream); + + /* parse Forwarded-count-value */ + try { + parseIntegerValue(pduDataStream); + } catch(RuntimeException e) { + log(headerField + " is not Integer-Value"); + return null; + } + + /* parse Encoded-string-value */ + EncodedStringValue previouslySentBy = + parseEncodedStringValue(pduDataStream); + if (null != previouslySentBy) { + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField + + " value: " + previouslySentBy.getString()); + } + headers.setEncodedStringValue(previouslySentBy, + PduHeaders.PREVIOUSLY_SENT_BY); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + break; + } + + case PduHeaders.PREVIOUSLY_SENT_DATE: { + /* Previously-sent-date-value = + * Value-length Forwarded-count-value Date-value */ + /* parse value-length */ + parseValueLength(pduDataStream); + + /* parse Forwarded-count-value */ + try { + parseIntegerValue(pduDataStream); + } catch(RuntimeException e) { + log(headerField + " is not Integer-Value"); + return null; + } + + /* Date-value */ + try { + long perviouslySentDate = parseLongInteger(pduDataStream); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField + + " value: " + perviouslySentDate); + } + headers.setLongInteger(perviouslySentDate, + PduHeaders.PREVIOUSLY_SENT_DATE); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + case PduHeaders.MM_FLAGS: { + /* MM-flags-value = + * Value-length + * ( Add-token | Remove-token | Filter-token ) + * Encoded-string-value + */ + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField + + " NOT REALLY SUPPORTED"); + } + + /* parse Value-length */ + parseValueLength(pduDataStream); + + /* Add-token | Remove-token | Filter-token */ + extractByteValue(pduDataStream); + + /* Encoded-string-value */ + parseEncodedStringValue(pduDataStream); + + /* not store this header filed in "headers", + * because now PduHeaders doesn't support it */ + break; + } + + /* Value-length + * (Message-total-token | Size-total-token) Integer-Value */ + case PduHeaders.MBOX_TOTALS: + case PduHeaders.MBOX_QUOTAS: + { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField); + } + /* Value-length */ + parseValueLength(pduDataStream); + + /* Message-total-token | Size-total-token */ + extractByteValue(pduDataStream); + + /*Integer-Value*/ + try { + parseIntegerValue(pduDataStream); + } catch(RuntimeException e) { + log(headerField + " is not Integer-Value"); + return null; + } + + /* not store these headers filed in "headers", + because now PduHeaders doesn't support them */ + break; + } + + case PduHeaders.ELEMENT_DESCRIPTOR: { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField); + } + parseContentType(pduDataStream, null); + + /* not store this header filed in "headers", + because now PduHeaders doesn't support it */ + break; + } + + case PduHeaders.CONTENT_TYPE: { + HashMap map = + new HashMap(); + byte[] contentType = + parseContentType(pduDataStream, map); + + if (null != contentType) { + try { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField + + contentType.toString()); + } + headers.setTextString(contentType, PduHeaders.CONTENT_TYPE); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } + + /* get start parameter */ + mStartParam = (byte[]) map.get(PduPart.P_START); + + /* get charset parameter */ + mTypeParam= (byte[]) map.get(PduPart.P_TYPE); + + keepParsing = false; + break; + } + + case PduHeaders.CONTENT: + case PduHeaders.ADDITIONAL_HEADERS: + case PduHeaders.ATTRIBUTES: + default: { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField); + } + log("Unknown header"); + } + } + } + + return headers; + } + + /** + * Parse pdu parts. + * + * @param pduDataStream pdu data input stream + * @return parts in PduBody structure + */ + protected PduBody parseParts(ByteArrayInputStream pduDataStream) { + if (pduDataStream == null) { + return null; + } + + int count = parseUnsignedInt(pduDataStream); // get the number of parts + PduBody body = new PduBody(); + + for (int i = 0 ; i < count ; i++) { + int headerLength = parseUnsignedInt(pduDataStream); + int dataLength = parseUnsignedInt(pduDataStream); + PduPart part = new PduPart(); + int startPos = pduDataStream.available(); + if (startPos <= 0) { + // Invalid part. + return null; + } + + /* parse part's content-type */ + HashMap map = new HashMap(); + byte[] contentType = parseContentType(pduDataStream, map); + if (null != contentType) { + part.setContentType(contentType); + } else { + part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*" + } + + /* get name parameter */ + byte[] name = (byte[]) map.get(PduPart.P_NAME); + if (null != name) { + part.setName(name); + } + + /* get charset parameter */ + Integer charset = (Integer) map.get(PduPart.P_CHARSET); + if (null != charset) { + part.setCharset(charset); + } + + /* parse part's headers */ + int endPos = pduDataStream.available(); + int partHeaderLen = headerLength - (startPos - endPos); + if (partHeaderLen > 0) { + if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) { + // Parse part header faild. + return null; + } + } else if (partHeaderLen < 0) { + // Invalid length of content-type. + return null; + } + + /* FIXME: check content-id, name, filename and content location, + * if not set anyone of them, generate a default content-location + */ + if ((null == part.getContentLocation()) + && (null == part.getName()) + && (null == part.getFilename()) + && (null == part.getContentId())) { + part.setContentLocation(Long.toOctalString( + System.currentTimeMillis()).getBytes()); + } + + /* get part's data */ + if (dataLength > 0) { + byte[] partData = new byte[dataLength]; + String partContentType = new String(part.getContentType()); + pduDataStream.read(partData, 0, dataLength); + if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) { + // parse "multipart/vnd.wap.multipart.alternative". + PduBody childBody = parseParts(new ByteArrayInputStream(partData)); + // take the first part of children. + part = childBody.getPart(0); + } else { + // Check Content-Transfer-Encoding. + byte[] partDataEncoding = part.getContentTransferEncoding(); + if (null != partDataEncoding) { + String encoding = new String(partDataEncoding); + if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { + // Decode "base64" into "binary". + partData = Base64.decodeBase64(partData); + } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { + // Decode "quoted-printable" into "binary". + partData = QuotedPrintable.decodeQuotedPrintable(partData); + } else { + // "binary" is the default encoding. + } + } + if (null == partData) { + log("Decode part data error!"); + return null; + } + part.setData(partData); + } + } + + /* add this part to body */ + if (THE_FIRST_PART == checkPartPosition(part)) { + /* this is the first part */ + body.addPart(0, part); + } else { + /* add the part to the end */ + body.addPart(part); + } + } + + return body; + } + + /** + * Log status. + * + * @param text log information + */ + @UnsupportedAppUsage + private static void log(String text) { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, text); + } + } + + /** + * Parse unsigned integer. + * + * @param pduDataStream pdu data input stream + * @return the integer, -1 when failed + */ + @UnsupportedAppUsage + protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * The maximum size of a uintvar is 32 bits. + * So it will be encoded in no more than 5 octets. + */ + assert(null != pduDataStream); + int result = 0; + int temp = pduDataStream.read(); + if (temp == -1) { + return temp; + } + + while((temp & 0x80) != 0) { + result = result << 7; + result |= temp & 0x7F; + temp = pduDataStream.read(); + if (temp == -1) { + return temp; + } + } + + result = result << 7; + result |= temp & 0x7F; + + return result; + } + + /** + * Parse value length. + * + * @param pduDataStream pdu data input stream + * @return the integer + */ + @UnsupportedAppUsage + protected static int parseValueLength(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Value-length = Short-length | (Length-quote Length) + * Short-length = + * Length-quote = + * Length = Uintvar-integer + * Uintvar-integer = 1*5 OCTET + */ + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + int first = temp & 0xFF; + + if (first <= SHORT_LENGTH_MAX) { + return first; + } else if (first == LENGTH_QUOTE) { + return parseUnsignedInt(pduDataStream); + } + + throw new RuntimeException ("Value length > LENGTH_QUOTE!"); + } + + /** + * Parse encoded string value. + * + * @param pduDataStream pdu data input stream + * @return the EncodedStringValue + */ + protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){ + /** + * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf + * Encoded-string-value = Text-string | Value-length Char-set Text-string + */ + assert(null != pduDataStream); + pduDataStream.mark(1); + EncodedStringValue returnValue = null; + int charset = 0; + int temp = pduDataStream.read(); + assert(-1 != temp); + int first = temp & 0xFF; + if (first == 0) { + return new EncodedStringValue(""); + } + + pduDataStream.reset(); + if (first < TEXT_MIN) { + parseValueLength(pduDataStream); + + charset = parseShortInteger(pduDataStream); //get the "Charset" + } + + byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING); + + try { + if (0 != charset) { + returnValue = new EncodedStringValue(charset, textString); + } else { + returnValue = new EncodedStringValue(textString); + } + } catch(Exception e) { + return null; + } + + return returnValue; + } + + /** + * Parse Text-String or Quoted-String. + * + * @param pduDataStream pdu data input stream + * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING + * @return the string without End-of-string in byte array + */ + @UnsupportedAppUsage + protected static byte[] parseWapString(ByteArrayInputStream pduDataStream, + int stringType) { + assert(null != pduDataStream); + /** + * From wap-230-wsp-20010705-a.pdf + * Text-string = [Quote] *TEXT End-of-string + * If the first character in the TEXT is in the range of 128-255, + * a Quote character must precede it. + * Otherwise the Quote character must be omitted. + * The Quote is not part of the contents. + * Quote = + * End-of-string = + * + * Quoted-string = *TEXT End-of-string + * + * Token-text = Token End-of-string + */ + + // Mark supposed beginning of Text-string + // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG + pduDataStream.mark(1); + + // Check first char + int temp = pduDataStream.read(); + assert(-1 != temp); + if ((TYPE_QUOTED_STRING == stringType) && + (QUOTED_STRING_FLAG == temp)) { + // Mark again if QUOTED_STRING_FLAG and ignore it + pduDataStream.mark(1); + } else if ((TYPE_TEXT_STRING == stringType) && + (QUOTE == temp)) { + // Mark again if QUOTE and ignore it + pduDataStream.mark(1); + } else { + // Otherwise go back to origin + pduDataStream.reset(); + } + + // We are now definitely at the beginning of string + /** + * Return *TOKEN or *TEXT (Text-String without QUOTE, + * Quoted-String without QUOTED_STRING_FLAG and without End-of-string) + */ + return getWapString(pduDataStream, stringType); + } + + /** + * Check TOKEN data defined in RFC2616. + * @param ch checking data + * @return true when ch is TOKEN, false when ch is not TOKEN + */ + protected static boolean isTokenCharacter(int ch) { + /** + * Token = 1* + * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64) + * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34) + * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61) + * | "{"(123) | "}"(125) | SP(32) | HT(9) + * CHAR = + * CTL = + * SP = + * HT = + */ + if((ch < 33) || (ch > 126)) { + return false; + } + + switch(ch) { + case '"': /* '"' */ + case '(': /* '(' */ + case ')': /* ')' */ + case ',': /* ',' */ + case '/': /* '/' */ + case ':': /* ':' */ + case ';': /* ';' */ + case '<': /* '<' */ + case '=': /* '=' */ + case '>': /* '>' */ + case '?': /* '?' */ + case '@': /* '@' */ + case '[': /* '[' */ + case '\\': /* '\' */ + case ']': /* ']' */ + case '{': /* '{' */ + case '}': /* '}' */ + return false; + } + + return true; + } + + /** + * Check TEXT data defined in RFC2616. + * @param ch checking data + * @return true when ch is TEXT, false when ch is not TEXT + */ + protected static boolean isText(int ch) { + /** + * TEXT = + * CTL = + * LWS = [CRLF] 1*( SP | HT ) + * CRLF = CR LF + * CR = + * LF = + */ + if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) { + return true; + } + + switch(ch) { + case '\t': /* '\t' */ + case '\n': /* '\n' */ + case '\r': /* '\r' */ + return true; + } + + return false; + } + + protected static byte[] getWapString(ByteArrayInputStream pduDataStream, + int stringType) { + assert(null != pduDataStream); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int temp = pduDataStream.read(); + assert(-1 != temp); + while((-1 != temp) && ('\0' != temp)) { + // check each of the character + if (stringType == TYPE_TOKEN_STRING) { + if (isTokenCharacter(temp)) { + out.write(temp); + } + } else { + if (isText(temp)) { + out.write(temp); + } + } + + temp = pduDataStream.read(); + assert(-1 != temp); + } + + if (out.size() > 0) { + return out.toByteArray(); + } + + return null; + } + + /** + * Extract a byte value from the input stream. + * + * @param pduDataStream pdu data input stream + * @return the byte + */ + protected static int extractByteValue(ByteArrayInputStream pduDataStream) { + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + return temp & 0xFF; + } + + /** + * Parse Short-Integer. + * + * @param pduDataStream pdu data input stream + * @return the byte + */ + @UnsupportedAppUsage + protected static int parseShortInteger(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Short-integer = OCTET + * Integers in range 0-127 shall be encoded as a one + * octet value with the most significant bit set to one (1xxx xxxx) + * and with the value in the remaining least significant bits. + */ + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + return temp & 0x7F; + } + + /** + * Parse Long-Integer. + * + * @param pduDataStream pdu data input stream + * @return long integer + */ + protected static long parseLongInteger(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Long-integer = Short-length Multi-octet-integer + * The Short-length indicates the length of the Multi-octet-integer + * Multi-octet-integer = 1*30 OCTET + * The content octets shall be an unsigned integer value + * with the most significant octet encoded first (big-endian representation). + * The minimum number of octets must be used to encode the value. + * Short-length = + */ + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + int count = temp & 0xFF; + + if (count > LONG_INTEGER_LENGTH_MAX) { + throw new RuntimeException("Octet count greater than 8 and I can't represent that!"); + } + + long result = 0; + + for (int i = 0 ; i < count ; i++) { + temp = pduDataStream.read(); + assert(-1 != temp); + result <<= 8; + result += (temp & 0xFF); + } + + return result; + } + + /** + * Parse Integer-Value. + * + * @param pduDataStream pdu data input stream + * @return long integer + */ + protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Integer-Value = Short-integer | Long-integer + */ + assert(null != pduDataStream); + pduDataStream.mark(1); + int temp = pduDataStream.read(); + assert(-1 != temp); + pduDataStream.reset(); + if (temp > SHORT_INTEGER_MAX) { + return parseShortInteger(pduDataStream); + } else { + return parseLongInteger(pduDataStream); + } + } + + /** + * To skip length of the wap value. + * + * @param pduDataStream pdu data input stream + * @param length area size + * @return the values in this area + */ + protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) { + assert(null != pduDataStream); + byte[] area = new byte[length]; + int readLen = pduDataStream.read(area, 0, length); + if (readLen < length) { //The actually read length is lower than the length + return -1; + } else { + return readLen; + } + } + + /** + * Parse content type parameters. For now we just support + * four parameters used in mms: "type", "start", "name", "charset". + * + * @param pduDataStream pdu data input stream + * @param map to store parameters of Content-Type field + * @param length length of all the parameters + */ + protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream, + HashMap map, Integer length) { + /** + * From wap-230-wsp-20010705-a.pdf + * Parameter = Typed-parameter | Untyped-parameter + * Typed-parameter = Well-known-parameter-token Typed-value + * the actual expected type of the value is implied by the well-known parameter + * Well-known-parameter-token = Integer-value + * the code values used for parameters are specified in the Assigned Numbers appendix + * Typed-value = Compact-value | Text-value + * In addition to the expected type, there may be no value. + * If the value cannot be encoded using the expected type, it shall be encoded as text. + * Compact-value = Integer-value | + * Date-value | Delta-seconds-value | Q-value | Version-value | + * Uri-value + * Untyped-parameter = Token-text Untyped-value + * the type of the value is unknown, but it shall be encoded as an integer, + * if that is possible. + * Untyped-value = Integer-value | Text-value + */ + assert(null != pduDataStream); + assert(length > 0); + + int startPos = pduDataStream.available(); + int tempPos = 0; + int lastLen = length; + while(0 < lastLen) { + int param = pduDataStream.read(); + assert(-1 != param); + lastLen--; + + switch (param) { + /** + * From rfc2387, chapter 3.1 + * The type parameter must be specified and its value is the MIME media + * type of the "root" body part. It permits a MIME user agent to + * determine the content-type without reference to the enclosed body + * part. If the value of the type parameter and the root body part's + * content-type differ then the User Agent's behavior is undefined. + * + * From wap-230-wsp-20010705-a.pdf + * type = Constrained-encoding + * Constrained-encoding = Extension-Media | Short-integer + * Extension-media = *TEXT End-of-string + */ + case PduPart.P_TYPE: + case PduPart.P_CT_MR_TYPE: + pduDataStream.mark(1); + int first = extractByteValue(pduDataStream); + pduDataStream.reset(); + if (first > TEXT_MAX) { + // Short-integer (well-known type) + int index = parseShortInteger(pduDataStream); + + if (index < PduContentTypes.contentTypes.length) { + byte[] type = (PduContentTypes.contentTypes[index]).getBytes(); + map.put(PduPart.P_TYPE, type); + } else { + //not support this type, ignore it. + } + } else { + // Text-String (extension-media) + byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if ((null != type) && (null != map)) { + map.put(PduPart.P_TYPE, type); + } + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + + /** + * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3. + * Start Parameter Referring to Presentation + * + * From rfc2387, chapter 3.2 + * The start parameter, if given, is the content-ID of the compound + * object's "root". If not present the "root" is the first body part in + * the Multipart/Related entity. The "root" is the element the + * applications processes first. + * + * From wap-230-wsp-20010705-a.pdf + * start = Text-String + */ + case PduPart.P_START: + case PduPart.P_DEP_START: + byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if ((null != start) && (null != map)) { + map.put(PduPart.P_START, start); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + + /** + * From oma-ts-mms-conf-v1_3.pdf + * In creation, the character set SHALL be either us-ascii + * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode]. + * In retrieval, both us-ascii and utf-8 SHALL be supported. + * + * From wap-230-wsp-20010705-a.pdf + * charset = Well-known-charset|Text-String + * Well-known-charset = Any-charset | Integer-value + * Both are encoded using values from Character Set + * Assignments table in Assigned Numbers + * Any-charset = + * Equivalent to the special RFC2616 charset value "*" + */ + case PduPart.P_CHARSET: + pduDataStream.mark(1); + int firstValue = extractByteValue(pduDataStream); + pduDataStream.reset(); + //Check first char + if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) || + (END_STRING_FLAG == firstValue)) { + //Text-String (extension-charset) + byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING); + try { + int charsetInt = CharacterSets.getMibEnumValue( + new String(charsetStr)); + map.put(PduPart.P_CHARSET, charsetInt); + } catch (UnsupportedEncodingException e) { + // Not a well-known charset, use "*". + Log.e(LOG_TAG, Arrays.toString(charsetStr), e); + map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET); + } + } else { + //Well-known-charset + int charset = (int) parseIntegerValue(pduDataStream); + if (map != null) { + map.put(PduPart.P_CHARSET, charset); + } + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + + /** + * From oma-ts-mms-conf-v1_3.pdf + * A name for multipart object SHALL be encoded using name-parameter + * for Content-Type header in WSP multipart headers. + * + * From wap-230-wsp-20010705-a.pdf + * name = Text-String + */ + case PduPart.P_DEP_NAME: + case PduPart.P_NAME: + byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if ((null != name) && (null != map)) { + map.put(PduPart.P_NAME, name); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + default: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "Not supported Content-Type parameter"); + } + if (-1 == skipWapValue(pduDataStream, lastLen)) { + Log.e(LOG_TAG, "Corrupt Content-Type"); + } else { + lastLen = 0; + } + break; + } + } + + if (0 != lastLen) { + Log.e(LOG_TAG, "Corrupt Content-Type"); + } + } + + /** + * Parse content type. + * + * @param pduDataStream pdu data input stream + * @param map to store parameters in Content-Type header field + * @return Content-Type value + */ + @UnsupportedAppUsage + protected static byte[] parseContentType(ByteArrayInputStream pduDataStream, + HashMap map) { + /** + * From wap-230-wsp-20010705-a.pdf + * Content-type-value = Constrained-media | Content-general-form + * Content-general-form = Value-length Media-type + * Media-type = (Well-known-media | Extension-Media) *(Parameter) + */ + assert(null != pduDataStream); + + byte[] contentType = null; + pduDataStream.mark(1); + int temp = pduDataStream.read(); + assert(-1 != temp); + pduDataStream.reset(); + + int cur = (temp & 0xFF); + + if (cur < TEXT_MIN) { + int length = parseValueLength(pduDataStream); + int startPos = pduDataStream.available(); + pduDataStream.mark(1); + temp = pduDataStream.read(); + assert(-1 != temp); + pduDataStream.reset(); + int first = (temp & 0xFF); + + if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) { + contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); + } else if (first > TEXT_MAX) { + int index = parseShortInteger(pduDataStream); + + if (index < PduContentTypes.contentTypes.length) { //well-known type + contentType = (PduContentTypes.contentTypes[index]).getBytes(); + } else { + pduDataStream.reset(); + contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); + } + } else { + Log.e(LOG_TAG, "Corrupt content-type"); + return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" + } + + int endPos = pduDataStream.available(); + int parameterLen = length - (startPos - endPos); + if (parameterLen > 0) {//have parameters + parseContentTypeParams(pduDataStream, map, parameterLen); + } + + if (parameterLen < 0) { + Log.e(LOG_TAG, "Corrupt MMS message"); + return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" + } + } else if (cur <= TEXT_MAX) { + contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); + } else { + contentType = + (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes(); + } + + return contentType; + } + + /** + * Parse part's headers. + * + * @param pduDataStream pdu data input stream + * @param part to store the header informations of the part + * @param length length of the headers + * @return true if parse successfully, false otherwise + */ + @UnsupportedAppUsage + protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream, + PduPart part, int length) { + assert(null != pduDataStream); + assert(null != part); + assert(length > 0); + + /** + * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2. + * A name for multipart object SHALL be encoded using name-parameter + * for Content-Type header in WSP multipart headers. + * In decoding, name-parameter of Content-Type SHALL be used if available. + * If name-parameter of Content-Type is not available, + * filename parameter of Content-Disposition header SHALL be used if available. + * If neither name-parameter of Content-Type header nor filename parameter + * of Content-Disposition header is available, + * Content-Location header SHALL be used if available. + * + * Within SMIL part the reference to the media object parts SHALL use + * either Content-ID or Content-Location mechanism [RFC2557] + * and the corresponding WSP part headers in media object parts + * contain the corresponding definitions. + */ + int startPos = pduDataStream.available(); + int tempPos = 0; + int lastLen = length; + while(0 < lastLen) { + int header = pduDataStream.read(); + assert(-1 != header); + lastLen--; + + if (header > TEXT_MAX) { + // Number assigned headers. + switch (header) { + case PduPart.P_CONTENT_LOCATION: + /** + * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 + * Content-location-value = Uri-value + */ + byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != contentLocation) { + part.setContentLocation(contentLocation); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + case PduPart.P_CONTENT_ID: + /** + * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 + * Content-ID-value = Quoted-string + */ + byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING); + if (null != contentId) { + part.setContentId(contentId); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + case PduPart.P_DEP_CONTENT_DISPOSITION: + case PduPart.P_CONTENT_DISPOSITION: + /** + * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 + * Content-disposition-value = Value-length Disposition *(Parameter) + * Disposition = Form-data | Attachment | Inline | Token-text + * Form-data = + * Attachment = + * Inline = + */ + + /* + * some carrier mmsc servers do not support content_disposition + * field correctly + */ + if (mParseContentDisposition) { + int len = parseValueLength(pduDataStream); + pduDataStream.mark(1); + int thisStartPos = pduDataStream.available(); + int thisEndPos = 0; + int value = pduDataStream.read(); + + if (value == PduPart.P_DISPOSITION_FROM_DATA ) { + part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA); + } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) { + part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT); + } else if (value == PduPart.P_DISPOSITION_INLINE) { + part.setContentDisposition(PduPart.DISPOSITION_INLINE); + } else { + pduDataStream.reset(); + /* Token-text */ + part.setContentDisposition(parseWapString(pduDataStream + , TYPE_TEXT_STRING)); + } + + /* get filename parameter and skip other parameters */ + thisEndPos = pduDataStream.available(); + if (thisStartPos - thisEndPos < len) { + value = pduDataStream.read(); + if (value == PduPart.P_FILENAME) { //filename is text-string + part.setFilename(parseWapString(pduDataStream + , TYPE_TEXT_STRING)); + } + + /* skip other parameters */ + thisEndPos = pduDataStream.available(); + if (thisStartPos - thisEndPos < len) { + int last = len - (thisStartPos - thisEndPos); + byte[] temp = new byte[last]; + pduDataStream.read(temp, 0, last); + } + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + } + break; + default: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "Not supported Part headers: " + header); + } + if (-1 == skipWapValue(pduDataStream, lastLen)) { + Log.e(LOG_TAG, "Corrupt Part headers"); + return false; + } + lastLen = 0; + break; + } + } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) { + // Not assigned header. + byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING); + byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING); + + // Check the header whether it is "Content-Transfer-Encoding". + if (true == + PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) { + part.setContentTransferEncoding(tempValue); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + } else { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "Not supported Part headers: " + header); + } + // Skip all headers of this part. + if (-1 == skipWapValue(pduDataStream, lastLen)) { + Log.e(LOG_TAG, "Corrupt Part headers"); + return false; + } + lastLen = 0; + } + } + + if (0 != lastLen) { + Log.e(LOG_TAG, "Corrupt Part headers"); + return false; + } + + return true; + } + + /** + * Check the position of a specified part. + * + * @param part the part to be checked + * @return part position, THE_FIRST_PART when it's the + * first one, THE_LAST_PART when it's the last one. + */ + @UnsupportedAppUsage + private static int checkPartPosition(PduPart part) { + assert(null != part); + if ((null == mTypeParam) && + (null == mStartParam)) { + return THE_LAST_PART; + } + + /* check part's content-id */ + if (null != mStartParam) { + byte[] contentId = part.getContentId(); + if (null != contentId) { + if (true == Arrays.equals(mStartParam, contentId)) { + return THE_FIRST_PART; + } + } + // This is not the first part, so append to end (keeping the original order) + // Check b/19607294 for details of this change + return THE_LAST_PART; + } + + /* check part's content-type */ + if (null != mTypeParam) { + byte[] contentType = part.getContentType(); + if (null != contentType) { + if (true == Arrays.equals(mTypeParam, contentType)) { + return THE_FIRST_PART; + } + } + } + + return THE_LAST_PART; + } + + /** + * Check mandatory headers of a pdu. + * + * @param headers pdu headers + * @return true if the pdu has all of the mandatory headers, false otherwise. + */ + protected static boolean checkMandatoryHeader(PduHeaders headers) { + if (null == headers) { + return false; + } + + /* get message type */ + int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE); + + /* check Mms-Version field */ + int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION); + if (0 == mmsVersion) { + // Every message should have Mms-Version field. + return false; + } + + /* check mandatory header fields */ + switch (messageType) { + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + // Content-Type field. + byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); + if (null == srContentType) { + return false; + } + + // From field. + EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM); + if (null == srFrom) { + return false; + } + + // Transaction-Id field. + byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == srTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_SEND_CONF: + // Response-Status field. + int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS); + if (0 == scResponseStatus) { + return false; + } + + // Transaction-Id field. + byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == scTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + // Content-Location field. + byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION); + if (null == niContentLocation) { + return false; + } + + // Expiry field. + long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY); + if (-1 == niExpiry) { + return false; + } + + // Message-Class field. + byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS); + if (null == niMessageClass) { + return false; + } + + // Message-Size field. + long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE); + if (-1 == niMessageSize) { + return false; + } + + // Transaction-Id field. + byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == niTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + // Status field. + int nriStatus = headers.getOctet(PduHeaders.STATUS); + if (0 == nriStatus) { + return false; + } + + // Transaction-Id field. + byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == nriTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + // Content-Type field. + byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); + if (null == rcContentType) { + return false; + } + + // Date field. + long rcDate = headers.getLongInteger(PduHeaders.DATE); + if (-1 == rcDate) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: + // Date field. + long diDate = headers.getLongInteger(PduHeaders.DATE); + if (-1 == diDate) { + return false; + } + + // Message-Id field. + byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); + if (null == diMessageId) { + return false; + } + + // Status field. + int diStatus = headers.getOctet(PduHeaders.STATUS); + if (0 == diStatus) { + return false; + } + + // To field. + EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO); + if (null == diTo) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + // Transaction-Id field. + byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == aiTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: + // Date field. + long roDate = headers.getLongInteger(PduHeaders.DATE); + if (-1 == roDate) { + return false; + } + + // From field. + EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM); + if (null == roFrom) { + return false; + } + + // Message-Id field. + byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); + if (null == roMessageId) { + return false; + } + + // Read-Status field. + int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS); + if (0 == roReadStatus) { + return false; + } + + // To field. + EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO); + if (null == roTo) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + // From field. + EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM); + if (null == rrFrom) { + return false; + } + + // Message-Id field. + byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); + if (null == rrMessageId) { + return false; + } + + // Read-Status field. + int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS); + if (0 == rrReadStatus) { + return false; + } + + // To field. + EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO); + if (null == rrTo) { + return false; + } + + break; + default: + // Parser doesn't support this message type in this version. + return false; + } + + return true; + } +} diff --git a/telephony/common/com/google/android/mms/pdu/PduPart.java b/telephony/common/com/google/android/mms/pdu/PduPart.java new file mode 100644 index 000000000000..09b775118dc3 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduPart.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.net.Uri; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.util.HashMap; +import java.util.Map; + +/** + * The pdu part. + */ +public class PduPart { + /** + * Well-Known Parameters. + */ + public static final int P_Q = 0x80; + public static final int P_CHARSET = 0x81; + public static final int P_LEVEL = 0x82; + public static final int P_TYPE = 0x83; + public static final int P_DEP_NAME = 0x85; + public static final int P_DEP_FILENAME = 0x86; + public static final int P_DIFFERENCES = 0x87; + public static final int P_PADDING = 0x88; + // This value of "TYPE" s used with Content-Type: multipart/related + public static final int P_CT_MR_TYPE = 0x89; + public static final int P_DEP_START = 0x8A; + public static final int P_DEP_START_INFO = 0x8B; + public static final int P_DEP_COMMENT = 0x8C; + public static final int P_DEP_DOMAIN = 0x8D; + public static final int P_MAX_AGE = 0x8E; + public static final int P_DEP_PATH = 0x8F; + public static final int P_SECURE = 0x90; + public static final int P_SEC = 0x91; + public static final int P_MAC = 0x92; + public static final int P_CREATION_DATE = 0x93; + public static final int P_MODIFICATION_DATE = 0x94; + public static final int P_READ_DATE = 0x95; + public static final int P_SIZE = 0x96; + public static final int P_NAME = 0x97; + public static final int P_FILENAME = 0x98; + public static final int P_START = 0x99; + public static final int P_START_INFO = 0x9A; + public static final int P_COMMENT = 0x9B; + public static final int P_DOMAIN = 0x9C; + public static final int P_PATH = 0x9D; + + /** + * Header field names. + */ + public static final int P_CONTENT_TYPE = 0x91; + public static final int P_CONTENT_LOCATION = 0x8E; + public static final int P_CONTENT_ID = 0xC0; + public static final int P_DEP_CONTENT_DISPOSITION = 0xAE; + public static final int P_CONTENT_DISPOSITION = 0xC5; + // The next header is unassigned header, use reserved header(0x48) value. + public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8; + + /** + * Content=Transfer-Encoding string. + */ + public static final String CONTENT_TRANSFER_ENCODING = + "Content-Transfer-Encoding"; + + /** + * Value of Content-Transfer-Encoding. + */ + public static final String P_BINARY = "binary"; + public static final String P_7BIT = "7bit"; + public static final String P_8BIT = "8bit"; + public static final String P_BASE64 = "base64"; + public static final String P_QUOTED_PRINTABLE = "quoted-printable"; + + /** + * Value of disposition can be set to PduPart when the value is octet in + * the PDU. + * "from-data" instead of Form-data. + * "attachment" instead of Attachment. + * "inline" instead of Inline. + */ + static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes(); + static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes(); + static final byte[] DISPOSITION_INLINE = "inline".getBytes(); + + /** + * Content-Disposition value. + */ + public static final int P_DISPOSITION_FROM_DATA = 0x80; + public static final int P_DISPOSITION_ATTACHMENT = 0x81; + public static final int P_DISPOSITION_INLINE = 0x82; + + /** + * Header of part. + */ + private Map mPartHeader = null; + + /** + * Data uri. + */ + private Uri mUri = null; + + /** + * Part data. + */ + private byte[] mPartData = null; + + private static final String TAG = "PduPart"; + + /** + * Empty Constructor. + */ + @UnsupportedAppUsage + public PduPart() { + mPartHeader = new HashMap(); + } + + /** + * Set part data. The data are stored as byte array. + * + * @param data the data + */ + @UnsupportedAppUsage + public void setData(byte[] data) { + if(data == null) { + return; + } + + mPartData = new byte[data.length]; + System.arraycopy(data, 0, mPartData, 0, data.length); + } + + /** + * @return A copy of the part data or null if the data wasn't set or + * the data is stored as Uri. + * @see #getDataUri + */ + @UnsupportedAppUsage + public byte[] getData() { + if(mPartData == null) { + return null; + } + + byte[] byteArray = new byte[mPartData.length]; + System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length); + return byteArray; + } + + /** + * @return The length of the data, if this object have data, else 0. + */ + @UnsupportedAppUsage + public int getDataLength() { + if(mPartData != null){ + return mPartData.length; + } else { + return 0; + } + } + + + /** + * Set data uri. The data are stored as Uri. + * + * @param uri the uri + */ + @UnsupportedAppUsage + public void setDataUri(Uri uri) { + mUri = uri; + } + + /** + * @return The Uri of the part data or null if the data wasn't set or + * the data is stored as byte array. + * @see #getData + */ + @UnsupportedAppUsage + public Uri getDataUri() { + return mUri; + } + + /** + * Set Content-id value + * + * @param contentId the content-id value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentId(byte[] contentId) { + if((contentId == null) || (contentId.length == 0)) { + throw new IllegalArgumentException( + "Content-Id may not be null or empty."); + } + + if ((contentId.length > 1) + && ((char) contentId[0] == '<') + && ((char) contentId[contentId.length - 1] == '>')) { + mPartHeader.put(P_CONTENT_ID, contentId); + return; + } + + // Insert beginning '<' and trailing '>' for Content-Id. + byte[] buffer = new byte[contentId.length + 2]; + buffer[0] = (byte) (0xff & '<'); + buffer[buffer.length - 1] = (byte) (0xff & '>'); + System.arraycopy(contentId, 0, buffer, 1, contentId.length); + mPartHeader.put(P_CONTENT_ID, buffer); + } + + /** + * Get Content-id value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentId() { + return (byte[]) mPartHeader.get(P_CONTENT_ID); + } + + /** + * Set Char-set value. + * + * @param charset the value + */ + @UnsupportedAppUsage + public void setCharset(int charset) { + mPartHeader.put(P_CHARSET, charset); + } + + /** + * Get Char-set value + * + * @return the charset value. Return 0 if charset was not set. + */ + @UnsupportedAppUsage + public int getCharset() { + Integer charset = (Integer) mPartHeader.get(P_CHARSET); + if(charset == null) { + return 0; + } else { + return charset.intValue(); + } + } + + /** + * Set Content-Location value. + * + * @param contentLocation the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentLocation(byte[] contentLocation) { + if(contentLocation == null) { + throw new NullPointerException("null content-location"); + } + + mPartHeader.put(P_CONTENT_LOCATION, contentLocation); + } + + /** + * Get Content-Location value. + * + * @return the value + * return PduPart.disposition[0] instead of (Form-data). + * return PduPart.disposition[1] instead of (Attachment). + * return PduPart.disposition[2] instead of (Inline). + */ + @UnsupportedAppUsage + public byte[] getContentLocation() { + return (byte[]) mPartHeader.get(P_CONTENT_LOCATION); + } + + /** + * Set Content-Disposition value. + * Use PduPart.disposition[0] instead of (Form-data). + * Use PduPart.disposition[1] instead of (Attachment). + * Use PduPart.disposition[2] instead of (Inline). + * + * @param contentDisposition the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentDisposition(byte[] contentDisposition) { + if(contentDisposition == null) { + throw new NullPointerException("null content-disposition"); + } + + mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition); + } + + /** + * Get Content-Disposition value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentDisposition() { + return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION); + } + + /** + * Set Content-Type value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentType(byte[] contentType) { + if(contentType == null) { + throw new NullPointerException("null content-type"); + } + + mPartHeader.put(P_CONTENT_TYPE, contentType); + } + + /** + * Get Content-Type value of part. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentType() { + return (byte[]) mPartHeader.get(P_CONTENT_TYPE); + } + + /** + * Set Content-Transfer-Encoding value + * + * @param contentId the content-id value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentTransferEncoding(byte[] contentTransferEncoding) { + if(contentTransferEncoding == null) { + throw new NullPointerException("null content-transfer-encoding"); + } + + mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding); + } + + /** + * Get Content-Transfer-Encoding value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentTransferEncoding() { + return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING); + } + + /** + * Set Content-type parameter: name. + * + * @param name the name value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setName(byte[] name) { + if(null == name) { + throw new NullPointerException("null content-id"); + } + + mPartHeader.put(P_NAME, name); + } + + /** + * Get content-type parameter: name. + * + * @return the name + */ + @UnsupportedAppUsage + public byte[] getName() { + return (byte[]) mPartHeader.get(P_NAME); + } + + /** + * Get Content-disposition parameter: filename + * + * @param fileName the filename value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setFilename(byte[] fileName) { + if(null == fileName) { + throw new NullPointerException("null content-id"); + } + + mPartHeader.put(P_FILENAME, fileName); + } + + /** + * Set Content-disposition parameter: filename + * + * @return the filename + */ + @UnsupportedAppUsage + public byte[] getFilename() { + return (byte[]) mPartHeader.get(P_FILENAME); + } + + @UnsupportedAppUsage + public String generateLocation() { + // Assumption: At least one of the content-location / name / filename + // or content-id should be set. This is guaranteed by the PduParser + // for incoming messages and by MM composer for outgoing messages. + byte[] location = (byte[]) mPartHeader.get(P_NAME); + if(null == location) { + location = (byte[]) mPartHeader.get(P_FILENAME); + + if (null == location) { + location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION); + } + } + + if (null == location) { + byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID); + return "cid:" + new String(contentId); + } else { + return new String(location); + } + } +} + diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java new file mode 100755 index 000000000000..93f30657bf1b --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/PduPersister.java @@ -0,0 +1,1573 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.drm.DrmManagerClient; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.Telephony; +import android.provider.Telephony.Mms; +import android.provider.Telephony.Mms.Addr; +import android.provider.Telephony.Mms.Part; +import android.provider.Telephony.MmsSms; +import android.provider.Telephony.MmsSms.PendingMessages; +import android.provider.Telephony.Threads; +import android.telephony.PhoneNumberUtils; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.ContentType; +import com.google.android.mms.InvalidHeaderValueException; +import com.google.android.mms.MmsException; +import com.google.android.mms.util.DownloadDrmHelper; +import com.google.android.mms.util.DrmConvertSession; +import com.google.android.mms.util.PduCache; +import com.google.android.mms.util.PduCacheEntry; +import com.google.android.mms.util.SqliteWrapper; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * This class is the high-level manager of PDU storage. + */ +public class PduPersister { + private static final String TAG = "PduPersister"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + private static final long DUMMY_THREAD_ID = Long.MAX_VALUE; + + /** + * The uri of temporary drm objects. + */ + public static final String TEMPORARY_DRM_OBJECT_URI = + "content://mms/" + Long.MAX_VALUE + "/part"; + /** + * Indicate that we transiently failed to process a MM. + */ + public static final int PROC_STATUS_TRANSIENT_FAILURE = 1; + /** + * Indicate that we permanently failed to process a MM. + */ + public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2; + /** + * Indicate that we have successfully processed a MM. + */ + public static final int PROC_STATUS_COMPLETED = 3; + + private static PduPersister sPersister; + @UnsupportedAppUsage + private static final PduCache PDU_CACHE_INSTANCE; + + @UnsupportedAppUsage + private static final int[] ADDRESS_FIELDS = new int[] { + PduHeaders.BCC, + PduHeaders.CC, + PduHeaders.FROM, + PduHeaders.TO + }; + + private static final String[] PDU_PROJECTION = new String[] { + Mms._ID, + Mms.MESSAGE_BOX, + Mms.THREAD_ID, + Mms.RETRIEVE_TEXT, + Mms.SUBJECT, + Mms.CONTENT_LOCATION, + Mms.CONTENT_TYPE, + Mms.MESSAGE_CLASS, + Mms.MESSAGE_ID, + Mms.RESPONSE_TEXT, + Mms.TRANSACTION_ID, + Mms.CONTENT_CLASS, + Mms.DELIVERY_REPORT, + Mms.MESSAGE_TYPE, + Mms.MMS_VERSION, + Mms.PRIORITY, + Mms.READ_REPORT, + Mms.READ_STATUS, + Mms.REPORT_ALLOWED, + Mms.RETRIEVE_STATUS, + Mms.STATUS, + Mms.DATE, + Mms.DELIVERY_TIME, + Mms.EXPIRY, + Mms.MESSAGE_SIZE, + Mms.SUBJECT_CHARSET, + Mms.RETRIEVE_TEXT_CHARSET, + }; + + private static final int PDU_COLUMN_ID = 0; + private static final int PDU_COLUMN_MESSAGE_BOX = 1; + private static final int PDU_COLUMN_THREAD_ID = 2; + private static final int PDU_COLUMN_RETRIEVE_TEXT = 3; + private static final int PDU_COLUMN_SUBJECT = 4; + private static final int PDU_COLUMN_CONTENT_LOCATION = 5; + private static final int PDU_COLUMN_CONTENT_TYPE = 6; + private static final int PDU_COLUMN_MESSAGE_CLASS = 7; + private static final int PDU_COLUMN_MESSAGE_ID = 8; + private static final int PDU_COLUMN_RESPONSE_TEXT = 9; + private static final int PDU_COLUMN_TRANSACTION_ID = 10; + private static final int PDU_COLUMN_CONTENT_CLASS = 11; + private static final int PDU_COLUMN_DELIVERY_REPORT = 12; + private static final int PDU_COLUMN_MESSAGE_TYPE = 13; + private static final int PDU_COLUMN_MMS_VERSION = 14; + private static final int PDU_COLUMN_PRIORITY = 15; + private static final int PDU_COLUMN_READ_REPORT = 16; + private static final int PDU_COLUMN_READ_STATUS = 17; + private static final int PDU_COLUMN_REPORT_ALLOWED = 18; + private static final int PDU_COLUMN_RETRIEVE_STATUS = 19; + private static final int PDU_COLUMN_STATUS = 20; + private static final int PDU_COLUMN_DATE = 21; + private static final int PDU_COLUMN_DELIVERY_TIME = 22; + private static final int PDU_COLUMN_EXPIRY = 23; + private static final int PDU_COLUMN_MESSAGE_SIZE = 24; + private static final int PDU_COLUMN_SUBJECT_CHARSET = 25; + private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26; + + @UnsupportedAppUsage + private static final String[] PART_PROJECTION = new String[] { + Part._ID, + Part.CHARSET, + Part.CONTENT_DISPOSITION, + Part.CONTENT_ID, + Part.CONTENT_LOCATION, + Part.CONTENT_TYPE, + Part.FILENAME, + Part.NAME, + Part.TEXT + }; + + private static final int PART_COLUMN_ID = 0; + private static final int PART_COLUMN_CHARSET = 1; + private static final int PART_COLUMN_CONTENT_DISPOSITION = 2; + private static final int PART_COLUMN_CONTENT_ID = 3; + private static final int PART_COLUMN_CONTENT_LOCATION = 4; + private static final int PART_COLUMN_CONTENT_TYPE = 5; + private static final int PART_COLUMN_FILENAME = 6; + private static final int PART_COLUMN_NAME = 7; + private static final int PART_COLUMN_TEXT = 8; + + @UnsupportedAppUsage + private static final HashMap MESSAGE_BOX_MAP; + // These map are used for convenience in persist() and load(). + private static final HashMap CHARSET_COLUMN_INDEX_MAP; + private static final HashMap ENCODED_STRING_COLUMN_INDEX_MAP; + private static final HashMap TEXT_STRING_COLUMN_INDEX_MAP; + private static final HashMap OCTET_COLUMN_INDEX_MAP; + private static final HashMap LONG_COLUMN_INDEX_MAP; + @UnsupportedAppUsage + private static final HashMap CHARSET_COLUMN_NAME_MAP; + @UnsupportedAppUsage + private static final HashMap ENCODED_STRING_COLUMN_NAME_MAP; + @UnsupportedAppUsage + private static final HashMap TEXT_STRING_COLUMN_NAME_MAP; + @UnsupportedAppUsage + private static final HashMap OCTET_COLUMN_NAME_MAP; + @UnsupportedAppUsage + private static final HashMap LONG_COLUMN_NAME_MAP; + + static { + MESSAGE_BOX_MAP = new HashMap(); + MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX); + MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT); + MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS); + MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX); + + CHARSET_COLUMN_INDEX_MAP = new HashMap(); + CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET); + CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET); + + CHARSET_COLUMN_NAME_MAP = new HashMap(); + CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET); + CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET); + + // Encoded string field code -> column index/name map. + ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap(); + ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT); + ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT); + + ENCODED_STRING_COLUMN_NAME_MAP = new HashMap(); + ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT); + ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT); + + // Text string field code -> column index/name map. + TEXT_STRING_COLUMN_INDEX_MAP = new HashMap(); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID); + + TEXT_STRING_COLUMN_NAME_MAP = new HashMap(); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID); + + // Octet field code -> column index/name map. + OCTET_COLUMN_INDEX_MAP = new HashMap(); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS); + + OCTET_COLUMN_NAME_MAP = new HashMap(); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS); + + // Long field code -> column index/name map. + LONG_COLUMN_INDEX_MAP = new HashMap(); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE); + + LONG_COLUMN_NAME_MAP = new HashMap(); + LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE); + LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME); + LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY); + LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE); + + PDU_CACHE_INSTANCE = PduCache.getInstance(); + } + + @UnsupportedAppUsage + private final Context mContext; + @UnsupportedAppUsage + private final ContentResolver mContentResolver; + private final DrmManagerClient mDrmManagerClient; + @UnsupportedAppUsage + private final TelephonyManager mTelephonyManager; + + private PduPersister(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + mDrmManagerClient = new DrmManagerClient(context); + mTelephonyManager = (TelephonyManager)context + .getSystemService(Context.TELEPHONY_SERVICE); + } + + /** Get(or create if not exist) an instance of PduPersister */ + @UnsupportedAppUsage + public static PduPersister getPduPersister(Context context) { + if ((sPersister == null)) { + sPersister = new PduPersister(context); + } else if (!context.equals(sPersister.mContext)) { + sPersister.release(); + sPersister = new PduPersister(context); + } + + return sPersister; + } + + private void setEncodedStringValueToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) { + String s = c.getString(columnIndex); + if ((s != null) && (s.length() > 0)) { + int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn); + int charset = c.getInt(charsetColumnIndex); + EncodedStringValue value = new EncodedStringValue( + charset, getBytes(s)); + headers.setEncodedStringValue(value, mapColumn); + } + } + + private void setTextStringToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) { + String s = c.getString(columnIndex); + if (s != null) { + headers.setTextString(getBytes(s), mapColumn); + } + } + + private void setOctetToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) throws InvalidHeaderValueException { + if (!c.isNull(columnIndex)) { + int b = c.getInt(columnIndex); + headers.setOctet(b, mapColumn); + } + } + + private void setLongToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) { + if (!c.isNull(columnIndex)) { + long l = c.getLong(columnIndex); + headers.setLongInteger(l, mapColumn); + } + } + + @UnsupportedAppUsage + private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) { + if (!c.isNull(columnIndex)) { + return c.getInt(columnIndex); + } + return null; + } + + @UnsupportedAppUsage + private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) { + if (!c.isNull(columnIndex)) { + return getBytes(c.getString(columnIndex)); + } + return null; + } + + private PduPart[] loadParts(long msgId) throws MmsException { + Cursor c = SqliteWrapper.query(mContext, mContentResolver, + Uri.parse("content://mms/" + msgId + "/part"), + PART_PROJECTION, null, null, null); + + PduPart[] parts = null; + + try { + if ((c == null) || (c.getCount() == 0)) { + if (LOCAL_LOGV) { + Log.v(TAG, "loadParts(" + msgId + "): no part to load."); + } + return null; + } + + int partCount = c.getCount(); + int partIdx = 0; + parts = new PduPart[partCount]; + while (c.moveToNext()) { + PduPart part = new PduPart(); + Integer charset = getIntegerFromPartColumn( + c, PART_COLUMN_CHARSET); + if (charset != null) { + part.setCharset(charset); + } + + byte[] contentDisposition = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_DISPOSITION); + if (contentDisposition != null) { + part.setContentDisposition(contentDisposition); + } + + byte[] contentId = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_ID); + if (contentId != null) { + part.setContentId(contentId); + } + + byte[] contentLocation = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_LOCATION); + if (contentLocation != null) { + part.setContentLocation(contentLocation); + } + + byte[] contentType = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_TYPE); + if (contentType != null) { + part.setContentType(contentType); + } else { + throw new MmsException("Content-Type must be set."); + } + + byte[] fileName = getByteArrayFromPartColumn( + c, PART_COLUMN_FILENAME); + if (fileName != null) { + part.setFilename(fileName); + } + + byte[] name = getByteArrayFromPartColumn( + c, PART_COLUMN_NAME); + if (name != null) { + part.setName(name); + } + + // Construct a Uri for this part. + long partId = c.getLong(PART_COLUMN_ID); + Uri partURI = Uri.parse("content://mms/part/" + partId); + part.setDataUri(partURI); + + // For images/audio/video, we won't keep their data in Part + // because their renderer accept Uri as source. + String type = toIsoString(contentType); + if (!ContentType.isImageType(type) + && !ContentType.isAudioType(type) + && !ContentType.isVideoType(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = null; + + // Store simple string values directly in the database instead of an + // external file. This makes the text searchable and retrieval slightly + // faster. + if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type) + || ContentType.TEXT_HTML.equals(type)) { + String text = c.getString(PART_COLUMN_TEXT); + byte [] blob = new EncodedStringValue(text != null ? text : "") + .getTextString(); + baos.write(blob, 0, blob.length); + } else { + + try { + is = mContentResolver.openInputStream(partURI); + + byte[] buffer = new byte[256]; + int len = is.read(buffer); + while (len >= 0) { + baos.write(buffer, 0, len); + len = is.read(buffer); + } + } catch (IOException e) { + Log.e(TAG, "Failed to load part data", e); + c.close(); + throw new MmsException(e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close stream", e); + } // Ignore + } + } + } + part.setData(baos.toByteArray()); + } + parts[partIdx++] = part; + } + } finally { + if (c != null) { + c.close(); + } + } + + return parts; + } + + private void loadAddress(long msgId, PduHeaders headers) { + Cursor c = SqliteWrapper.query(mContext, mContentResolver, + Uri.parse("content://mms/" + msgId + "/addr"), + new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE }, + null, null, null); + + if (c != null) { + try { + while (c.moveToNext()) { + String addr = c.getString(0); + if (!TextUtils.isEmpty(addr)) { + int addrType = c.getInt(2); + switch (addrType) { + case PduHeaders.FROM: + headers.setEncodedStringValue( + new EncodedStringValue(c.getInt(1), getBytes(addr)), + addrType); + break; + case PduHeaders.TO: + case PduHeaders.CC: + case PduHeaders.BCC: + headers.appendEncodedStringValue( + new EncodedStringValue(c.getInt(1), getBytes(addr)), + addrType); + break; + default: + Log.e(TAG, "Unknown address type: " + addrType); + break; + } + } + } + } finally { + c.close(); + } + } + } + + /** + * Load a PDU from storage by given Uri. + * + * @param uri The Uri of the PDU to be loaded. + * @return A generic PDU object, it may be cast to dedicated PDU. + * @throws MmsException Failed to load some fields of a PDU. + */ + @UnsupportedAppUsage + public GenericPdu load(Uri uri) throws MmsException { + GenericPdu pdu = null; + PduCacheEntry cacheEntry = null; + int msgBox = 0; + long threadId = -1; + try { + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "load: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + return cacheEntry.getPdu(); + } + } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); + } + + Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, + PDU_PROJECTION, null, null, null); + PduHeaders headers = new PduHeaders(); + Set> set; + long msgId = ContentUris.parseId(uri); + + try { + if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { + throw new MmsException("Bad uri: " + uri); + } + + msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); + threadId = c.getLong(PDU_COLUMN_THREAD_ID); + + set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setEncodedStringValueToHeaders( + c, e.getValue(), headers, e.getKey()); + } + + set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setTextStringToHeaders( + c, e.getValue(), headers, e.getKey()); + } + + set = OCTET_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setOctetToHeaders( + c, e.getValue(), headers, e.getKey()); + } + + set = LONG_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setLongToHeaders( + c, e.getValue(), headers, e.getKey()); + } + } finally { + if (c != null) { + c.close(); + } + } + + // Check whether 'msgId' has been assigned a valid value. + if (msgId == -1L) { + throw new MmsException("Error! ID of the message: -1."); + } + + // Load address information of the MM. + loadAddress(msgId, headers); + + int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); + PduBody body = new PduBody(); + + // For PDU which type is M_retrieve.conf or Send.req, we should + // load multiparts and put them into the body of the PDU. + if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) + || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { + PduPart[] parts = loadParts(msgId); + if (parts != null) { + int partsNum = parts.length; + for (int i = 0; i < partsNum; i++) { + body.addPart(parts[i]); + } + } + } + + switch (msgType) { + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + pdu = new NotificationInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: + pdu = new DeliveryInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: + pdu = new ReadOrigInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + pdu = new RetrieveConf(headers, body); + break; + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + pdu = new SendReq(headers, body); + break; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + pdu = new AcknowledgeInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + pdu = new NotifyRespInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + pdu = new ReadRecInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_SEND_CONF: + case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: + case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: + case PduHeaders.MESSAGE_TYPE_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: + case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: + throw new MmsException( + "Unsupported PDU type: " + Integer.toHexString(msgType)); + + default: + throw new MmsException( + "Unrecognized PDU type: " + Integer.toHexString(msgType)); + } + } finally { + synchronized(PDU_CACHE_INSTANCE) { + if (pdu != null) { + assert(PDU_CACHE_INSTANCE.get(uri) == null); + // Update the cache entry with the real info + cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); + PDU_CACHE_INSTANCE.put(uri, cacheEntry); + } + PDU_CACHE_INSTANCE.setUpdating(uri, false); + PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead + } + } + return pdu; + } + + @UnsupportedAppUsage + private void persistAddress( + long msgId, int type, EncodedStringValue[] array) { + ContentValues values = new ContentValues(3); + + for (EncodedStringValue addr : array) { + values.clear(); // Clear all values first. + values.put(Addr.ADDRESS, toIsoString(addr.getTextString())); + values.put(Addr.CHARSET, addr.getCharacterSet()); + values.put(Addr.TYPE, type); + + Uri uri = Uri.parse("content://mms/" + msgId + "/addr"); + SqliteWrapper.insert(mContext, mContentResolver, uri, values); + } + } + + @UnsupportedAppUsage + private static String getPartContentType(PduPart part) { + return part.getContentType() == null ? null : toIsoString(part.getContentType()); + } + + @UnsupportedAppUsage + public Uri persistPart(PduPart part, long msgId, HashMap preOpenedFiles) + throws MmsException { + Uri uri = Uri.parse("content://mms/" + msgId + "/part"); + ContentValues values = new ContentValues(8); + + int charset = part.getCharset(); + if (charset != 0 ) { + values.put(Part.CHARSET, charset); + } + + String contentType = getPartContentType(part); + if (contentType != null) { + // There is no "image/jpg" in Android (and it's an invalid mimetype). + // Change it to "image/jpeg" + if (ContentType.IMAGE_JPG.equals(contentType)) { + contentType = ContentType.IMAGE_JPEG; + } + + values.put(Part.CONTENT_TYPE, contentType); + // To ensure the SMIL part is always the first part. + if (ContentType.APP_SMIL.equals(contentType)) { + values.put(Part.SEQ, -1); + } + } else { + throw new MmsException("MIME type of the part must be set."); + } + + if (part.getFilename() != null) { + String fileName = new String(part.getFilename()); + values.put(Part.FILENAME, fileName); + } + + if (part.getName() != null) { + String name = new String(part.getName()); + values.put(Part.NAME, name); + } + + Object value = null; + if (part.getContentDisposition() != null) { + value = toIsoString(part.getContentDisposition()); + values.put(Part.CONTENT_DISPOSITION, (String) value); + } + + if (part.getContentId() != null) { + value = toIsoString(part.getContentId()); + values.put(Part.CONTENT_ID, (String) value); + } + + if (part.getContentLocation() != null) { + value = toIsoString(part.getContentLocation()); + values.put(Part.CONTENT_LOCATION, (String) value); + } + + Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); + if (res == null) { + throw new MmsException("Failed to persist part, return null."); + } + + persistData(part, res, contentType, preOpenedFiles); + // After successfully store the data, we should update + // the dataUri of the part. + part.setDataUri(res); + + return res; + } + + /** + * Save data of the part into storage. The source data may be given + * by a byte[] or a Uri. If it's a byte[], directly save it + * into storage, otherwise load source data from the dataUri and then + * save it. If the data is an image, we may scale down it according + * to user preference. + * + * @param part The PDU part which contains data to be saved. + * @param uri The URI of the part. + * @param contentType The MIME type of the part. + * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. + * @throws MmsException Cannot find source data or error occurred + * while saving the data. + */ + private void persistData(PduPart part, Uri uri, + String contentType, HashMap preOpenedFiles) + throws MmsException { + OutputStream os = null; + InputStream is = null; + DrmConvertSession drmConvertSession = null; + Uri dataUri = null; + String path = null; + + try { + byte[] data = part.getData(); + if (ContentType.TEXT_PLAIN.equals(contentType) + || ContentType.APP_SMIL.equals(contentType) + || ContentType.TEXT_HTML.equals(contentType)) { + ContentValues cv = new ContentValues(); + if (data == null) { + data = new String("").getBytes(CharacterSets.DEFAULT_CHARSET_NAME); + } + cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString()); + if (mContentResolver.update(uri, cv, null, null) != 1) { + throw new MmsException("unable to update " + uri.toString()); + } + } else { + boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType); + if (isDrm) { + if (uri != null) { + try (ParcelFileDescriptor pfd = + mContentResolver.openFileDescriptor(uri, "r")) { + if (pfd.getStatSize() > 0) { + // we're not going to re-persist and re-encrypt an already + // converted drm file + return; + } + } catch (Exception e) { + Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e); + } + } + // We haven't converted the file yet, start the conversion + drmConvertSession = DrmConvertSession.open(mContext, contentType); + if (drmConvertSession == null) { + throw new MmsException("Mimetype " + contentType + + " can not be converted."); + } + } + // uri can look like: + // content://mms/part/98 + os = mContentResolver.openOutputStream(uri); + if (data == null) { + dataUri = part.getDataUri(); + if ((dataUri == null) || (dataUri.equals(uri))) { + Log.w(TAG, "Can't find data for this part."); + return; + } + // dataUri can look like: + // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715586 + if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) { + is = preOpenedFiles.get(dataUri); + } + if (is == null) { + is = mContentResolver.openInputStream(dataUri); + } + + if (LOCAL_LOGV) { + Log.v(TAG, "Saving data to: " + uri); + } + + byte[] buffer = new byte[8192]; + for (int len = 0; (len = is.read(buffer)) != -1; ) { + if (!isDrm) { + os.write(buffer, 0, len); + } else { + byte[] convertedData = drmConvertSession.convert(buffer, len); + if (convertedData != null) { + os.write(convertedData, 0, convertedData.length); + } else { + throw new MmsException("Error converting drm data."); + } + } + } + } else { + if (LOCAL_LOGV) { + Log.v(TAG, "Saving data to: " + uri); + } + if (!isDrm) { + os.write(data); + } else { + dataUri = uri; + byte[] convertedData = drmConvertSession.convert(data, data.length); + if (convertedData != null) { + os.write(convertedData, 0, convertedData.length); + } else { + throw new MmsException("Error converting drm data."); + } + } + } + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to open Input/Output stream.", e); + throw new MmsException(e); + } catch (IOException e) { + Log.e(TAG, "Failed to read/write data.", e); + throw new MmsException(e); + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e) { + Log.e(TAG, "IOException while closing: " + os, e); + } // Ignore + } + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(TAG, "IOException while closing: " + is, e); + } // Ignore + } + if (drmConvertSession != null) { + drmConvertSession.close(path); + + // Reset the permissions on the encrypted part file so everyone has only read + // permission. + File f = new File(path); + ContentValues values = new ContentValues(0); + SqliteWrapper.update(mContext, mContentResolver, + Uri.parse("content://mms/resetFilePerm/" + f.getName()), + values, null, null); + } + } + } + + @UnsupportedAppUsage + private void updateAddress( + long msgId, int type, EncodedStringValue[] array) { + // Delete old address information and then insert new ones. + SqliteWrapper.delete(mContext, mContentResolver, + Uri.parse("content://mms/" + msgId + "/addr"), + Addr.TYPE + "=" + type, null); + + persistAddress(msgId, type, array); + } + + /** + * Update headers of a SendReq. + * + * @param uri The PDU which need to be updated. + * @param pdu New headers. + * @throws MmsException Bad URI or updating failed. + */ + @UnsupportedAppUsage + public void updateHeaders(Uri uri, SendReq sendReq) { + synchronized(PDU_CACHE_INSTANCE) { + // If the cache item is getting updated, wait until it's done updating before + // purging it. + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "updateHeaders: ", e); + } + } + } + PDU_CACHE_INSTANCE.purge(uri); + + ContentValues values = new ContentValues(10); + byte[] contentType = sendReq.getContentType(); + if (contentType != null) { + values.put(Mms.CONTENT_TYPE, toIsoString(contentType)); + } + + long date = sendReq.getDate(); + if (date != -1) { + values.put(Mms.DATE, date); + } + + int deliveryReport = sendReq.getDeliveryReport(); + if (deliveryReport != 0) { + values.put(Mms.DELIVERY_REPORT, deliveryReport); + } + + long expiry = sendReq.getExpiry(); + if (expiry != -1) { + values.put(Mms.EXPIRY, expiry); + } + + byte[] msgClass = sendReq.getMessageClass(); + if (msgClass != null) { + values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass)); + } + + int priority = sendReq.getPriority(); + if (priority != 0) { + values.put(Mms.PRIORITY, priority); + } + + int readReport = sendReq.getReadReport(); + if (readReport != 0) { + values.put(Mms.READ_REPORT, readReport); + } + + byte[] transId = sendReq.getTransactionId(); + if (transId != null) { + values.put(Mms.TRANSACTION_ID, toIsoString(transId)); + } + + EncodedStringValue subject = sendReq.getSubject(); + if (subject != null) { + values.put(Mms.SUBJECT, toIsoString(subject.getTextString())); + values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet()); + } else { + values.put(Mms.SUBJECT, ""); + } + + long messageSize = sendReq.getMessageSize(); + if (messageSize > 0) { + values.put(Mms.MESSAGE_SIZE, messageSize); + } + + PduHeaders headers = sendReq.getPduHeaders(); + HashSet recipients = new HashSet(); + for (int addrType : ADDRESS_FIELDS) { + EncodedStringValue[] array = null; + if (addrType == PduHeaders.FROM) { + EncodedStringValue v = headers.getEncodedStringValue(addrType); + if (v != null) { + array = new EncodedStringValue[1]; + array[0] = v; + } + } else { + array = headers.getEncodedStringValues(addrType); + } + + if (array != null) { + long msgId = ContentUris.parseId(uri); + updateAddress(msgId, addrType, array); + if (addrType == PduHeaders.TO) { + for (EncodedStringValue v : array) { + if (v != null) { + recipients.add(v.getString()); + } + } + } + } + } + if (!recipients.isEmpty()) { + long threadId = Threads.getOrCreateThreadId(mContext, recipients); + values.put(Mms.THREAD_ID, threadId); + } + + SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); + } + + private void updatePart(Uri uri, PduPart part, HashMap preOpenedFiles) + throws MmsException { + ContentValues values = new ContentValues(7); + + int charset = part.getCharset(); + if (charset != 0 ) { + values.put(Part.CHARSET, charset); + } + + String contentType = null; + if (part.getContentType() != null) { + contentType = toIsoString(part.getContentType()); + values.put(Part.CONTENT_TYPE, contentType); + } else { + throw new MmsException("MIME type of the part must be set."); + } + + if (part.getFilename() != null) { + String fileName = new String(part.getFilename()); + values.put(Part.FILENAME, fileName); + } + + if (part.getName() != null) { + String name = new String(part.getName()); + values.put(Part.NAME, name); + } + + Object value = null; + if (part.getContentDisposition() != null) { + value = toIsoString(part.getContentDisposition()); + values.put(Part.CONTENT_DISPOSITION, (String) value); + } + + if (part.getContentId() != null) { + value = toIsoString(part.getContentId()); + values.put(Part.CONTENT_ID, (String) value); + } + + if (part.getContentLocation() != null) { + value = toIsoString(part.getContentLocation()); + values.put(Part.CONTENT_LOCATION, (String) value); + } + + SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); + + // Only update the data when: + // 1. New binary data supplied or + // 2. The Uri of the part is different from the current one. + if ((part.getData() != null) + || (!uri.equals(part.getDataUri()))) { + persistData(part, uri, contentType, preOpenedFiles); + } + } + + /** + * Update all parts of a PDU. + * + * @param uri The PDU which need to be updated. + * @param body New message body of the PDU. + * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. + * @throws MmsException Bad URI or updating failed. + */ + @UnsupportedAppUsage + public void updateParts(Uri uri, PduBody body, HashMap preOpenedFiles) + throws MmsException { + try { + PduCacheEntry cacheEntry; + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "updateParts: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body); + } + } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); + } + + ArrayList toBeCreated = new ArrayList(); + HashMap toBeUpdated = new HashMap(); + + int partsNum = body.getPartsNum(); + StringBuilder filter = new StringBuilder().append('('); + for (int i = 0; i < partsNum; i++) { + PduPart part = body.getPart(i); + Uri partUri = part.getDataUri(); + if ((partUri == null) || TextUtils.isEmpty(partUri.getAuthority()) + || !partUri.getAuthority().startsWith("mms")) { + toBeCreated.add(part); + } else { + toBeUpdated.put(partUri, part); + + // Don't use 'i > 0' to determine whether we should append + // 'AND' since 'i = 0' may be skipped in another branch. + if (filter.length() > 1) { + filter.append(" AND "); + } + + filter.append(Part._ID); + filter.append("!="); + DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment()); + } + } + filter.append(')'); + + long msgId = ContentUris.parseId(uri); + + // Remove the parts which doesn't exist anymore. + SqliteWrapper.delete(mContext, mContentResolver, + Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"), + filter.length() > 2 ? filter.toString() : null, null); + + // Create new parts which didn't exist before. + for (PduPart part : toBeCreated) { + persistPart(part, msgId, preOpenedFiles); + } + + // Update the modified parts. + for (Map.Entry e : toBeUpdated.entrySet()) { + updatePart(e.getKey(), e.getValue(), preOpenedFiles); + } + } finally { + synchronized(PDU_CACHE_INSTANCE) { + PDU_CACHE_INSTANCE.setUpdating(uri, false); + PDU_CACHE_INSTANCE.notifyAll(); + } + } + } + + /** + * Persist a PDU object to specific location in the storage. + * + * @param pdu The PDU object to be stored. + * @param uri Where to store the given PDU object. + * @param createThreadId if true, this function may create a thread id for the recipients + * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used + * to create the associated thread. When false, only the sender will be used in finding or + * creating the appropriate thread or conversation. + * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. + * @return A Uri which can be used to access the stored PDU. + */ + + @UnsupportedAppUsage + public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled, + HashMap preOpenedFiles) + throws MmsException { + if (uri == null) { + throw new MmsException("Uri may not be null."); + } + long msgId = -1; + try { + msgId = ContentUris.parseId(uri); + } catch (NumberFormatException e) { + // the uri ends with "inbox" or something else like that + } + boolean existingUri = msgId != -1; + + if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) { + throw new MmsException( + "Bad destination, must be one of " + + "content://mms/inbox, content://mms/sent, " + + "content://mms/drafts, content://mms/outbox, " + + "content://mms/temp."); + } + synchronized(PDU_CACHE_INSTANCE) { + // If the cache item is getting updated, wait until it's done updating before + // purging it. + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "persist: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "persist1: ", e); + } + } + } + PDU_CACHE_INSTANCE.purge(uri); + + PduHeaders header = pdu.getPduHeaders(); + PduBody body = null; + ContentValues values = new ContentValues(); + Set> set; + + set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set) { + int field = e.getKey(); + EncodedStringValue encodedString = header.getEncodedStringValue(field); + if (encodedString != null) { + String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field); + values.put(e.getValue(), toIsoString(encodedString.getTextString())); + values.put(charsetColumn, encodedString.getCharacterSet()); + } + } + + set = TEXT_STRING_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set){ + byte[] text = header.getTextString(e.getKey()); + if (text != null) { + values.put(e.getValue(), toIsoString(text)); + } + } + + set = OCTET_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set){ + int b = header.getOctet(e.getKey()); + if (b != 0) { + values.put(e.getValue(), b); + } + } + + set = LONG_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set){ + long l = header.getLongInteger(e.getKey()); + if (l != -1L) { + values.put(e.getValue(), l); + } + } + + HashMap addressMap = + new HashMap(ADDRESS_FIELDS.length); + // Save address information. + for (int addrType : ADDRESS_FIELDS) { + EncodedStringValue[] array = null; + if (addrType == PduHeaders.FROM) { + EncodedStringValue v = header.getEncodedStringValue(addrType); + if (v != null) { + array = new EncodedStringValue[1]; + array[0] = v; + } + } else { + array = header.getEncodedStringValues(addrType); + } + addressMap.put(addrType, array); + } + + HashSet recipients = new HashSet(); + int msgType = pdu.getMessageType(); + // Here we only allocate thread ID for M-Notification.ind, + // M-Retrieve.conf and M-Send.req. + // Some of other PDU types may be allocated a thread ID outside + // this scope. + if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) + || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) + || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { + switch (msgType) { + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + loadRecipients(PduHeaders.FROM, recipients, addressMap, false); + + // For received messages when group MMS is enabled, we want to associate this + // message with the thread composed of all the recipients -- all but our own + // number, that is. This includes the person who sent the + // message or the FROM field (above) in addition to the other people the message + // was addressed to or the TO field. Our own number is in that TO field and + // we have to ignore it in loadRecipients. + if (groupMmsEnabled) { + loadRecipients(PduHeaders.TO, recipients, addressMap, true); + + // Also load any numbers in the CC field to address group messaging + // compatibility issues with devices that place numbers in this field + // for group messages. + loadRecipients(PduHeaders.CC, recipients, addressMap, true); + } + break; + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + loadRecipients(PduHeaders.TO, recipients, addressMap, false); + break; + } + long threadId = 0; + if (createThreadId && !recipients.isEmpty()) { + // Given all the recipients associated with this message, find (or create) the + // correct thread. + threadId = Threads.getOrCreateThreadId(mContext, recipients); + } + values.put(Mms.THREAD_ID, threadId); + } + + // Save parts first to avoid inconsistent message is loaded + // while saving the parts. + long dummyId = System.currentTimeMillis(); // Dummy ID of the msg. + + // Figure out if this PDU is a text-only message + boolean textOnly = true; + + // Sum up the total message size + int messageSize = 0; + + // Get body if the PDU is a RetrieveConf or SendReq. + if (pdu instanceof MultimediaMessagePdu) { + body = ((MultimediaMessagePdu) pdu).getBody(); + // Start saving parts if necessary. + if (body != null) { + int partsNum = body.getPartsNum(); + if (partsNum > 2) { + // For a text-only message there will be two parts: 1-the SMIL, 2-the text. + // Down a few lines below we're checking to make sure we've only got SMIL or + // text. We also have to check then we don't have more than two parts. + // Otherwise, a slideshow with two text slides would be marked as textOnly. + textOnly = false; + } + for (int i = 0; i < partsNum; i++) { + PduPart part = body.getPart(i); + messageSize += part.getDataLength(); + persistPart(part, dummyId, preOpenedFiles); + + // If we've got anything besides text/plain or SMIL part, then we've got + // an mms message with some other type of attachment. + String contentType = getPartContentType(part); + if (contentType != null && !ContentType.APP_SMIL.equals(contentType) + && !ContentType.TEXT_PLAIN.equals(contentType)) { + textOnly = false; + } + } + } + } + // Record whether this mms message is a simple plain text or not. This is a hint for the + // UI. + values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0); + // The message-size might already have been inserted when parsing the + // PDU header. If not, then we insert the message size as well. + if (values.getAsInteger(Mms.MESSAGE_SIZE) == null) { + values.put(Mms.MESSAGE_SIZE, messageSize); + } + + Uri res = null; + if (existingUri) { + res = uri; + SqliteWrapper.update(mContext, mContentResolver, res, values, null, null); + } else { + res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); + if (res == null) { + throw new MmsException("persist() failed: return null."); + } + // Get the real ID of the PDU and update all parts which were + // saved with the dummy ID. + msgId = ContentUris.parseId(res); + } + + values = new ContentValues(1); + values.put(Part.MSG_ID, msgId); + SqliteWrapper.update(mContext, mContentResolver, + Uri.parse("content://mms/" + dummyId + "/part"), + values, null, null); + // We should return the longest URI of the persisted PDU, for + // example, if input URI is "content://mms/inbox" and the _ID of + // persisted PDU is '8', we should return "content://mms/inbox/8" + // instead of "content://mms/8". + // FIXME: Should the MmsProvider be responsible for this??? + if (!existingUri) { + res = Uri.parse(uri + "/" + msgId); + } + + // Save address information. + for (int addrType : ADDRESS_FIELDS) { + EncodedStringValue[] array = addressMap.get(addrType); + if (array != null) { + persistAddress(msgId, addrType, array); + } + } + + return res; + } + + /** + * For a given address type, extract the recipients from the headers. + * + * @param addressType can be PduHeaders.FROM, PduHeaders.TO or PduHeaders.CC + * @param recipients a HashSet that is loaded with the recipients from the FROM, TO or CC headers + * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header + * @param excludeMyNumber if true, the number of this phone will be excluded from recipients + */ + @UnsupportedAppUsage + private void loadRecipients(int addressType, HashSet recipients, + HashMap addressMap, boolean excludeMyNumber) { + EncodedStringValue[] array = addressMap.get(addressType); + if (array == null) { + return; + } + // If the TO recipients is only a single address, then we can skip loadRecipients when + // we're excluding our own number because we know that address is our own. + if (excludeMyNumber && array.length == 1) { + return; + } + final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext); + final Set myPhoneNumbers = new HashSet(); + if (excludeMyNumber) { + // Build a list of my phone numbers from the various sims. + for (int subid : subscriptionManager.getActiveSubscriptionIdList()) { + final String myNumber = mTelephonyManager.getLine1Number(subid); + if (myNumber != null) { + myPhoneNumbers.add(myNumber); + } + } + } + + for (EncodedStringValue v : array) { + if (v != null) { + final String number = v.getString(); + if (excludeMyNumber) { + for (final String myNumber : myPhoneNumbers) { + if (!PhoneNumberUtils.compare(number, myNumber) + && !recipients.contains(number)) { + // Only add numbers which aren't my own number. + recipients.add(number); + break; + } + } + } else if (!recipients.contains(number)){ + recipients.add(number); + } + } + } + } + + /** + * Move a PDU object from one location to another. + * + * @param from Specify the PDU object to be moved. + * @param to The destination location, should be one of the following: + * "content://mms/inbox", "content://mms/sent", + * "content://mms/drafts", "content://mms/outbox", + * "content://mms/trash". + * @return New Uri of the moved PDU. + * @throws MmsException Error occurred while moving the message. + */ + @UnsupportedAppUsage + public Uri move(Uri from, Uri to) throws MmsException { + // Check whether the 'msgId' has been assigned a valid value. + long msgId = ContentUris.parseId(from); + if (msgId == -1L) { + throw new MmsException("Error! ID of the message: -1."); + } + + // Get corresponding int value of destination box. + Integer msgBox = MESSAGE_BOX_MAP.get(to); + if (msgBox == null) { + throw new MmsException( + "Bad destination, must be one of " + + "content://mms/inbox, content://mms/sent, " + + "content://mms/drafts, content://mms/outbox, " + + "content://mms/temp."); + } + + ContentValues values = new ContentValues(1); + values.put(Mms.MESSAGE_BOX, msgBox); + SqliteWrapper.update(mContext, mContentResolver, from, values, null, null); + return ContentUris.withAppendedId(to, msgId); + } + + /** + * Wrap a byte[] into a String. + */ + @UnsupportedAppUsage + public static String toIsoString(byte[] bytes) { + try { + return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + // Impossible to reach here! + Log.e(TAG, "ISO_8859_1 must be supported!", e); + return ""; + } + } + + /** + * Unpack a given String into a byte[]. + */ + @UnsupportedAppUsage + public static byte[] getBytes(String data) { + try { + return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + // Impossible to reach here! + Log.e(TAG, "ISO_8859_1 must be supported!", e); + return new byte[0]; + } + } + + /** + * Remove all objects in the temporary path. + */ + public void release() { + Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI); + SqliteWrapper.delete(mContext, mContentResolver, uri, null, null); + } + + /** + * Find all messages to be sent or downloaded before certain time. + */ + @UnsupportedAppUsage + public Cursor getPendingMessages(long dueTime) { + Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); + uriBuilder.appendQueryParameter("protocol", "mms"); + + String selection = PendingMessages.ERROR_TYPE + " < ?" + + " AND " + PendingMessages.DUE_TIME + " <= ?"; + + String[] selectionArgs = new String[] { + String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT), + String.valueOf(dueTime) + }; + + return SqliteWrapper.query(mContext, mContentResolver, + uriBuilder.build(), null, selection, selectionArgs, + PendingMessages.DUE_TIME); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java new file mode 100644 index 000000000000..9d6535c72e90 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.io.ByteArrayOutputStream; + +public class QuotedPrintable { + private static byte ESCAPE_CHAR = '='; + + /** + * Decodes an array quoted-printable characters into an array of original bytes. + * Escaped characters are converted back to their original representation. + * + *

+ * This function implements a subset of + * quoted-printable encoding specification (rule #1 and rule #2) + * as defined in RFC 1521. + *

+ * + * @param bytes array of quoted-printable characters + * @return array of original bytes, + * null if quoted-printable decoding is unsuccessful. + */ + @UnsupportedAppUsage + public static final byte[] decodeQuotedPrintable(byte[] bytes) { + if (bytes == null) { + return null; + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b == ESCAPE_CHAR) { + try { + if('\r' == (char)bytes[i + 1] && + '\n' == (char)bytes[i + 2]) { + i += 2; + continue; + } + int u = Character.digit((char) bytes[++i], 16); + int l = Character.digit((char) bytes[++i], 16); + if (u == -1 || l == -1) { + return null; + } + buffer.write((char) ((u << 4) + l)); + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } else { + buffer.write(b); + } + } + return buffer.toByteArray(); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java new file mode 100644 index 000000000000..e38c62dde622 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +public class ReadOrigInd extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + public ReadOrigInd() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + ReadOrigInd(PduHeaders headers) { + super(headers); + } + + /** + * Get Date value. + * + * @return the value + */ + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value. + * + * @param value the value + */ + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get X-MMS-Read-status value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getReadStatus() { + return mPduHeaders.getOctet(PduHeaders.READ_STATUS); + } + + /** + * Set X-MMS-Read-status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReadStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); + } + + /** + * Get To value. + * + * @return the value + */ + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * Set To value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java new file mode 100644 index 000000000000..9696bc259d00 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +public class ReadRecInd extends GenericPdu { + /** + * Constructor, used when composing a M-ReadRec.ind pdu. + * + * @param from the from value + * @param messageId the message ID value + * @param mmsVersion current viersion of mms + * @param readStatus the read status value + * @param to the to value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if messageId or to is null. + */ + @UnsupportedAppUsage + public ReadRecInd(EncodedStringValue from, + byte[] messageId, + int mmsVersion, + int readStatus, + EncodedStringValue[] to) throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND); + setFrom(from); + setMessageId(messageId); + setMmsVersion(mmsVersion); + setTo(to); + setReadStatus(readStatus); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + ReadRecInd(PduHeaders headers) { + super(headers); + } + + /** + * Get Date value. + * + * @return the value + */ + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value. + * + * @param value the value + */ + @UnsupportedAppUsage + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get To value. + * + * @return the value + */ + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * Set To value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /** + * Get X-MMS-Read-status value. + * + * @return the value + */ + public int getReadStatus() { + return mPduHeaders.getOctet(PduHeaders.READ_STATUS); + } + + /** + * Set X-MMS-Read-status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReadStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java new file mode 100644 index 000000000000..03755af4189c --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Retrieve.conf Pdu. + */ +public class RetrieveConf extends MultimediaMessagePdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + @UnsupportedAppUsage + public RetrieveConf() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + RetrieveConf(PduHeaders headers) { + super(headers); + } + + /** + * Constructor with given headers and body + * + * @param headers Headers for this PDU. + * @param body Body of this PDu. + */ + @UnsupportedAppUsage + RetrieveConf(PduHeaders headers, PduBody body) { + super(headers, body); + } + + /** + * Get CC value. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue[] getCc() { + return mPduHeaders.getEncodedStringValues(PduHeaders.CC); + } + + /** + * Add a "CC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void addCc(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); + } + + /** + * Get Content-type value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentType() { + return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); + } + + /** + * Set Content-type value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentType(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); + } + + /** + * Get X-Mms-Delivery-Report value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getDeliveryReport() { + return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); + } + + /** + * Set X-Mms-Delivery-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setDeliveryReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } + + /** + * Get X-Mms-Message-Class value. + * Message-class-value = Class-identifier | Token-text + * Class-identifier = Personal | Advertisement | Informational | Auto + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageClass() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); + } + + /** + * Set X-Mms-Message-Class value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setMessageClass(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get X-Mms-Read-Report value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getReadReport() { + return mPduHeaders.getOctet(PduHeaders.READ_REPORT); + } + + /** + * Set X-Mms-Read-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setReadReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); + } + + /** + * Get X-Mms-Retrieve-Status value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getRetrieveStatus() { + return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS); + } + + /** + * Set X-Mms-Retrieve-Status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setRetrieveStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS); + } + + /** + * Get X-Mms-Retrieve-Text value. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue getRetrieveText() { + return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT); + } + + /** + * Set X-Mms-Retrieve-Text value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setRetrieveText(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT); + } + + /** + * Get X-Mms-Transaction-Id. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte getContentClass() {return 0x00;} + * public void setApplicId(byte value) {} + * + * public byte getDrmContent() {return 0x00;} + * public void setDrmContent(byte value) {} + * + * public byte getDistributionIndicator() {return 0x00;} + * public void setDistributionIndicator(byte value) {} + * + * public PreviouslySentByValue getPreviouslySentBy() {return null;} + * public void setPreviouslySentBy(PreviouslySentByValue value) {} + * + * public PreviouslySentDateValue getPreviouslySentDate() {} + * public void setPreviouslySentDate(PreviouslySentDateValue value) {} + * + * public MmFlagsValue getMmFlags() {return null;} + * public void setMmFlags(MmFlagsValue value) {} + * + * public MmStateValue getMmState() {return null;} + * public void getMmState(MmStateValue value) {} + * + * public byte[] getReplaceId() {return 0x00;} + * public void setReplaceId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getReplyCharging() {return 0x00;} + * public void setReplyCharging(byte value) {} + * + * public byte getReplyChargingDeadline() {return 0x00;} + * public void setReplyChargingDeadline(byte value) {} + * + * public byte[] getReplyChargingId() {return 0x00;} + * public void setReplyChargingId(byte[] value) {} + * + * public long getReplyChargingSize() {return 0;} + * public void setReplyChargingSize(long value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/SendConf.java b/telephony/common/com/google/android/mms/pdu/SendConf.java new file mode 100644 index 000000000000..b85982791ada --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/SendConf.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +public class SendConf extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + @UnsupportedAppUsage + public SendConf() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + @UnsupportedAppUsage + SendConf(PduHeaders headers) { + super(headers); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get X-Mms-Response-Status. + * + * @return the value + */ + @UnsupportedAppUsage + public int getResponseStatus() { + return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS); + } + + /** + * Set X-Mms-Response-Status. + * + * @param value the values + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setResponseStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + @UnsupportedAppUsage + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getContentLocation() {return null;} + * public void setContentLocation(byte[] value) {} + * + * public EncodedStringValue getResponseText() {return null;} + * public void setResponseText(EncodedStringValue value) {} + * + * public byte getStoreStatus() {return 0x00;} + * public void setStoreStatus(byte value) {} + * + * public byte[] getStoreStatusText() {return null;} + * public void setStoreStatusText(byte[] value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/SendReq.java b/telephony/common/com/google/android/mms/pdu/SendReq.java new file mode 100644 index 000000000000..c1b7f934c0f7 --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/SendReq.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.InvalidHeaderValueException; + +public class SendReq extends MultimediaMessagePdu { + private static final String TAG = "SendReq"; + + @UnsupportedAppUsage + public SendReq() { + super(); + + try { + setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); + setMmsVersion(PduHeaders.CURRENT_MMS_VERSION); + // FIXME: Content-type must be decided according to whether + // SMIL part present. + setContentType("application/vnd.wap.multipart.related".getBytes()); + setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes())); + setTransactionId(generateTransactionId()); + } catch (InvalidHeaderValueException e) { + // Impossible to reach here since all headers we set above are valid. + Log.e(TAG, "Unexpected InvalidHeaderValueException.", e); + throw new RuntimeException(e); + } + } + + private byte[] generateTransactionId() { + String transactionId = "T" + Long.toHexString(System.currentTimeMillis()); + return transactionId.getBytes(); + } + + /** + * Constructor, used when composing a M-Send.req pdu. + * + * @param contentType the content type value + * @param from the from value + * @param mmsVersion current viersion of mms + * @param transactionId the transaction-id value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if contentType, form or transactionId is null. + */ + public SendReq(byte[] contentType, + EncodedStringValue from, + int mmsVersion, + byte[] transactionId) throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); + setContentType(contentType); + setFrom(from); + setMmsVersion(mmsVersion); + setTransactionId(transactionId); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + SendReq(PduHeaders headers) { + super(headers); + } + + /** + * Constructor with given headers and body + * + * @param headers Headers for this PDU. + * @param body Body of this PDu. + */ + @UnsupportedAppUsage + SendReq(PduHeaders headers, PduBody body) { + super(headers, body); + } + + /** + * Get Bcc value. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue[] getBcc() { + return mPduHeaders.getEncodedStringValues(PduHeaders.BCC); + } + + /** + * Add a "BCC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void addBcc(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC); + } + + /** + * Set "BCC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setBcc(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC); + } + + /** + * Get CC value. + * + * @return the value + */ + @UnsupportedAppUsage + public EncodedStringValue[] getCc() { + return mPduHeaders.getEncodedStringValues(PduHeaders.CC); + } + + /** + * Add a "CC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void addCc(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); + } + + /** + * Set "CC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setCc(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.CC); + } + + /** + * Get Content-type value. + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getContentType() { + return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); + } + + /** + * Set Content-type value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setContentType(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); + } + + /** + * Get X-Mms-Delivery-Report value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getDeliveryReport() { + return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); + } + + /** + * Set X-Mms-Delivery-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setDeliveryReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); + } + + /** + * Get X-Mms-Expiry value. + * + * Expiry-value = Value-length + * (Absolute-token Date-value | Relative-token Delta-seconds-value) + * + * @return the value + */ + @UnsupportedAppUsage + public long getExpiry() { + return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); + } + + /** + * Set X-Mms-Expiry value. + * + * @param value the value + */ + @UnsupportedAppUsage + public void setExpiry(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); + } + + /** + * Get X-Mms-MessageSize value. + * + * Expiry-value = size of message + * + * @return the value + */ + @UnsupportedAppUsage + public long getMessageSize() { + return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); + } + + /** + * Set X-Mms-MessageSize value. + * + * @param value the value + */ + @UnsupportedAppUsage + public void setMessageSize(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); + } + + /** + * Get X-Mms-Message-Class value. + * Message-class-value = Class-identifier | Token-text + * Class-identifier = Personal | Advertisement | Informational | Auto + * + * @return the value + */ + @UnsupportedAppUsage + public byte[] getMessageClass() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); + } + + /** + * Set X-Mms-Message-Class value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setMessageClass(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); + } + + /** + * Get X-Mms-Read-Report value. + * + * @return the value + */ + @UnsupportedAppUsage + public int getReadReport() { + return mPduHeaders.getOctet(PduHeaders.READ_REPORT); + } + + /** + * Set X-Mms-Read-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + @UnsupportedAppUsage + public void setReadReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); + } + + /** + * Set "To" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + @UnsupportedAppUsage + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + @UnsupportedAppUsage + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /* + * Optional, not supported header fields: + * + * public byte getAdaptationAllowed() {return 0}; + * public void setAdaptationAllowed(btye value) {}; + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte getContentClass() {return 0x00;} + * public void setApplicId(byte value) {} + * + * public long getDeliveryTime() {return 0}; + * public void setDeliveryTime(long value) {}; + * + * public byte getDrmContent() {return 0x00;} + * public void setDrmContent(byte value) {} + * + * public MmFlagsValue getMmFlags() {return null;} + * public void setMmFlags(MmFlagsValue value) {} + * + * public MmStateValue getMmState() {return null;} + * public void getMmState(MmStateValue value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getReplyCharging() {return 0x00;} + * public void setReplyCharging(byte value) {} + * + * public byte getReplyChargingDeadline() {return 0x00;} + * public void setReplyChargingDeadline(byte value) {} + * + * public byte[] getReplyChargingId() {return 0x00;} + * public void setReplyChargingId(byte[] value) {} + * + * public long getReplyChargingSize() {return 0;} + * public void setReplyChargingSize(long value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getStore() {return 0x00;} + * public void setStore(byte value) {} + */ +} diff --git a/telephony/common/com/google/android/mms/pdu/package.html b/telephony/common/com/google/android/mms/pdu/package.html new file mode 100755 index 000000000000..c9f96a66ab3b --- /dev/null +++ b/telephony/common/com/google/android/mms/pdu/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/telephony/common/com/google/android/mms/util/AbstractCache.java b/telephony/common/com/google/android/mms/util/AbstractCache.java new file mode 100644 index 000000000000..ab5d48a4ce3d --- /dev/null +++ b/telephony/common/com/google/android/mms/util/AbstractCache.java @@ -0,0 +1,119 @@ +/* + * 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.google.android.mms.util; + +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.util.HashMap; + +public abstract class AbstractCache { + private static final String TAG = "AbstractCache"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + private static final int MAX_CACHED_ITEMS = 500; + + private final HashMap> mCacheMap; + + @UnsupportedAppUsage + protected AbstractCache() { + mCacheMap = new HashMap>(); + } + + @UnsupportedAppUsage + public boolean put(K key, V value) { + if (LOCAL_LOGV) { + Log.v(TAG, "Trying to put " + key + " into cache."); + } + + if (mCacheMap.size() >= MAX_CACHED_ITEMS) { + // TODO Should remove the oldest or least hit cached entry + // and then cache the new one. + if (LOCAL_LOGV) { + Log.v(TAG, "Failed! size limitation reached."); + } + return false; + } + + if (key != null) { + CacheEntry cacheEntry = new CacheEntry(); + cacheEntry.value = value; + mCacheMap.put(key, cacheEntry); + + if (LOCAL_LOGV) { + Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total."); + } + return true; + } + return false; + } + + @UnsupportedAppUsage + public V get(K key) { + if (LOCAL_LOGV) { + Log.v(TAG, "Trying to get " + key + " from cache."); + } + + if (key != null) { + CacheEntry cacheEntry = mCacheMap.get(key); + if (cacheEntry != null) { + cacheEntry.hit++; + if (LOCAL_LOGV) { + Log.v(TAG, key + " hit " + cacheEntry.hit + " times."); + } + return cacheEntry.value; + } + } + return null; + } + + @UnsupportedAppUsage + public V purge(K key) { + if (LOCAL_LOGV) { + Log.v(TAG, "Trying to purge " + key); + } + + CacheEntry v = mCacheMap.remove(key); + + if (LOCAL_LOGV) { + Log.v(TAG, mCacheMap.size() + " items cached."); + } + + return v != null ? v.value : null; + } + + @UnsupportedAppUsage + public void purgeAll() { + if (LOCAL_LOGV) { + Log.v(TAG, "Purging cache, " + mCacheMap.size() + + " items dropped."); + } + mCacheMap.clear(); + } + + public int size() { + return mCacheMap.size(); + } + + private static class CacheEntry { + int hit; + V value; + } +} diff --git a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java new file mode 100644 index 000000000000..118de465a518 --- /dev/null +++ b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 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.google.android.mms.util; + +import android.content.Context; +import android.drm.DrmManagerClient; +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +public class DownloadDrmHelper { + private static final String TAG = "DownloadDrmHelper"; + + /** The MIME type of special DRM files */ + public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message"; + + /** The extensions of special DRM files */ + public static final String EXTENSION_DRM_MESSAGE = ".dm"; + + public static final String EXTENSION_INTERNAL_FWDL = ".fl"; + + /** + * Checks if the Media Type is a DRM Media Type + * + * @param drmManagerClient A DrmManagerClient + * @param mimetype Media Type to check + * @return True if the Media Type is DRM else false + */ + public static boolean isDrmMimeType(Context context, String mimetype) { + boolean result = false; + if (context != null) { + try { + DrmManagerClient drmClient = new DrmManagerClient(context); + if (drmClient != null && mimetype != null && mimetype.length() > 0) { + result = drmClient.canHandle("", mimetype); + } + } catch (IllegalArgumentException e) { + Log.w(TAG, + "DrmManagerClient instance could not be created, context is Illegal."); + } catch (IllegalStateException e) { + Log.w(TAG, "DrmManagerClient didn't initialize properly."); + } + } + return result; + } + + /** + * Checks if the Media Type needs to be DRM converted + * + * @param mimetype Media type of the content + * @return True if convert is needed else false + */ + @UnsupportedAppUsage + public static boolean isDrmConvertNeeded(String mimetype) { + return MIMETYPE_DRM_MESSAGE.equals(mimetype); + } + + /** + * Modifies the file extension for a DRM Forward Lock file NOTE: This + * function shouldn't be called if the file shouldn't be DRM converted + */ + @UnsupportedAppUsage + public static String modifyDrmFwLockFileExtension(String filename) { + if (filename != null) { + int extensionIndex; + extensionIndex = filename.lastIndexOf("."); + if (extensionIndex != -1) { + filename = filename.substring(0, extensionIndex); + } + filename = filename.concat(EXTENSION_INTERNAL_FWDL); + } + return filename; + } + + /** + * Gets the original mime type of DRM protected content. + * + * @param context The context + * @param path Path to the file + * @param containingMime The current mime type of the file i.e. the + * containing mime type + * @return The original mime type of the file if DRM protected else the + * currentMime + */ + public static String getOriginalMimeType(Context context, String path, String containingMime) { + String result = containingMime; + DrmManagerClient drmClient = new DrmManagerClient(context); + try { + if (drmClient.canHandle(path, null)) { + result = drmClient.getOriginalMimeType(path); + } + } catch (IllegalArgumentException ex) { + Log.w(TAG, + "Can't get original mime type since path is null or empty string."); + } catch (IllegalStateException ex) { + Log.w(TAG, "DrmManagerClient didn't initialize properly."); + } + return result; + } +} diff --git a/telephony/common/com/google/android/mms/util/DrmConvertSession.java b/telephony/common/com/google/android/mms/util/DrmConvertSession.java new file mode 100644 index 000000000000..0e8ec91f4ef6 --- /dev/null +++ b/telephony/common/com/google/android/mms/util/DrmConvertSession.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2012 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.google.android.mms.util; + +import android.content.Context; +import android.drm.DrmConvertedStatus; +import android.drm.DrmManagerClient; +import android.provider.Downloads; +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + + +public class DrmConvertSession { + private DrmManagerClient mDrmClient; + private int mConvertSessionId; + private static final String TAG = "DrmConvertSession"; + + private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) { + mDrmClient = drmClient; + mConvertSessionId = convertSessionId; + } + + /** + * Start of converting a file. + * + * @param context The context of the application running the convert session. + * @param mimeType Mimetype of content that shall be converted. + * @return A convert session or null in case an error occurs. + */ + @UnsupportedAppUsage + public static DrmConvertSession open(Context context, String mimeType) { + DrmManagerClient drmClient = null; + int convertSessionId = -1; + if (context != null && mimeType != null && !mimeType.equals("")) { + try { + drmClient = new DrmManagerClient(context); + try { + convertSessionId = drmClient.openConvertSession(mimeType); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Conversion of Mimetype: " + mimeType + + " is not supported.", e); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not access Open DrmFramework.", e); + } + } catch (IllegalArgumentException e) { + Log.w(TAG, + "DrmManagerClient instance could not be created, context is Illegal."); + } catch (IllegalStateException e) { + Log.w(TAG, "DrmManagerClient didn't initialize properly."); + } + } + + if (drmClient == null || convertSessionId < 0) { + return null; + } else { + return new DrmConvertSession(drmClient, convertSessionId); + } + } + /** + * Convert a buffer of data to protected format. + * + * @param buffer Buffer filled with data to convert. + * @param size The number of bytes that shall be converted. + * @return A Buffer filled with converted data, if execution is ok, in all + * other case null. + */ + @UnsupportedAppUsage + public byte [] convert(byte[] inBuffer, int size) { + byte[] result = null; + if (inBuffer != null) { + DrmConvertedStatus convertedStatus = null; + try { + if (size != inBuffer.length) { + byte[] buf = new byte[size]; + System.arraycopy(inBuffer, 0, buf, 0, size); + convertedStatus = mDrmClient.convertData(mConvertSessionId, buf); + } else { + convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer); + } + + if (convertedStatus != null && + convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK && + convertedStatus.convertedData != null) { + result = convertedStatus.convertedData; + } + } catch (IllegalArgumentException e) { + Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: " + + mConvertSessionId, e); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not convert data. Convertsession: " + + mConvertSessionId, e); + } + } else { + throw new IllegalArgumentException("Parameter inBuffer is null"); + } + return result; + } + + /** + * Ends a conversion session of a file. + * + * @param fileName The filename of the converted file. + * @return Downloads.Impl.STATUS_SUCCESS if execution is ok. + * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not + * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem + * occurs when accessing drm framework. + * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred. + */ + @UnsupportedAppUsage + public int close(String filename) { + DrmConvertedStatus convertedStatus = null; + int result = Downloads.Impl.STATUS_UNKNOWN_ERROR; + if (mDrmClient != null && mConvertSessionId >= 0) { + try { + convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId); + if (convertedStatus == null || + convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK || + convertedStatus.convertedData == null) { + result = Downloads.Impl.STATUS_NOT_ACCEPTABLE; + } else { + RandomAccessFile rndAccessFile = null; + try { + rndAccessFile = new RandomAccessFile(filename, "rw"); + rndAccessFile.seek(convertedStatus.offset); + rndAccessFile.write(convertedStatus.convertedData); + result = Downloads.Impl.STATUS_SUCCESS; + } catch (FileNotFoundException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "File: " + filename + " could not be found.", e); + } catch (IOException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "Could not access File: " + filename + " .", e); + } catch (IllegalArgumentException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "Could not open file in mode: rw", e); + } catch (SecurityException e) { + Log.w(TAG, "Access to File: " + filename + + " was denied denied by SecurityManager.", e); + } finally { + if (rndAccessFile != null) { + try { + rndAccessFile.close(); + } catch (IOException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "Failed to close File:" + filename + + ".", e); + } + } + } + } + } catch (IllegalStateException e) { + Log.w(TAG, "Could not close convertsession. Convertsession: " + + mConvertSessionId, e); + } + } + return result; + } +} diff --git a/telephony/common/com/google/android/mms/util/PduCache.java b/telephony/common/com/google/android/mms/util/PduCache.java new file mode 100644 index 000000000000..94e38946f632 --- /dev/null +++ b/telephony/common/com/google/android/mms/util/PduCache.java @@ -0,0 +1,268 @@ +/* + * 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.google.android.mms.util; + +import android.content.ContentUris; +import android.content.UriMatcher; +import android.net.Uri; +import android.provider.Telephony.Mms; +import android.util.Log; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.util.HashMap; +import java.util.HashSet; + +public final class PduCache extends AbstractCache { + private static final String TAG = "PduCache"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + private static final int MMS_ALL = 0; + private static final int MMS_ALL_ID = 1; + private static final int MMS_INBOX = 2; + private static final int MMS_INBOX_ID = 3; + private static final int MMS_SENT = 4; + private static final int MMS_SENT_ID = 5; + private static final int MMS_DRAFTS = 6; + private static final int MMS_DRAFTS_ID = 7; + private static final int MMS_OUTBOX = 8; + private static final int MMS_OUTBOX_ID = 9; + private static final int MMS_CONVERSATION = 10; + private static final int MMS_CONVERSATION_ID = 11; + + private static final UriMatcher URI_MATCHER; + private static final HashMap MATCH_TO_MSGBOX_ID_MAP; + + private static PduCache sInstance; + + static { + URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + URI_MATCHER.addURI("mms", null, MMS_ALL); + URI_MATCHER.addURI("mms", "#", MMS_ALL_ID); + URI_MATCHER.addURI("mms", "inbox", MMS_INBOX); + URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID); + URI_MATCHER.addURI("mms", "sent", MMS_SENT); + URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID); + URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS); + URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID); + URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX); + URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID); + URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION); + URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID); + + MATCH_TO_MSGBOX_ID_MAP = new HashMap(); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX); + } + + private final HashMap> mMessageBoxes; + private final HashMap> mThreads; + private final HashSet mUpdating; + + @UnsupportedAppUsage + private PduCache() { + mMessageBoxes = new HashMap>(); + mThreads = new HashMap>(); + mUpdating = new HashSet(); + } + + @UnsupportedAppUsage + synchronized public static final PduCache getInstance() { + if (sInstance == null) { + if (LOCAL_LOGV) { + Log.v(TAG, "Constructing new PduCache instance."); + } + sInstance = new PduCache(); + } + return sInstance; + } + + @Override + synchronized public boolean put(Uri uri, PduCacheEntry entry) { + int msgBoxId = entry.getMessageBox(); + HashSet msgBox = mMessageBoxes.get(msgBoxId); + if (msgBox == null) { + msgBox = new HashSet(); + mMessageBoxes.put(msgBoxId, msgBox); + } + + long threadId = entry.getThreadId(); + HashSet thread = mThreads.get(threadId); + if (thread == null) { + thread = new HashSet(); + mThreads.put(threadId, thread); + } + + Uri finalKey = normalizeKey(uri); + boolean result = super.put(finalKey, entry); + if (result) { + msgBox.add(finalKey); + thread.add(finalKey); + } + setUpdating(uri, false); + return result; + } + + synchronized public void setUpdating(Uri uri, boolean updating) { + if (updating) { + mUpdating.add(uri); + } else { + mUpdating.remove(uri); + } + } + + @UnsupportedAppUsage + synchronized public boolean isUpdating(Uri uri) { + return mUpdating.contains(uri); + } + + @Override + @UnsupportedAppUsage + synchronized public PduCacheEntry purge(Uri uri) { + int match = URI_MATCHER.match(uri); + switch (match) { + case MMS_ALL_ID: + return purgeSingleEntry(uri); + case MMS_INBOX_ID: + case MMS_SENT_ID: + case MMS_DRAFTS_ID: + case MMS_OUTBOX_ID: + String msgId = uri.getLastPathSegment(); + return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId)); + // Implicit batch of purge, return null. + case MMS_ALL: + case MMS_CONVERSATION: + purgeAll(); + return null; + case MMS_INBOX: + case MMS_SENT: + case MMS_DRAFTS: + case MMS_OUTBOX: + purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match)); + return null; + case MMS_CONVERSATION_ID: + purgeByThreadId(ContentUris.parseId(uri)); + return null; + default: + return null; + } + } + + private PduCacheEntry purgeSingleEntry(Uri key) { + mUpdating.remove(key); + PduCacheEntry entry = super.purge(key); + if (entry != null) { + removeFromThreads(key, entry); + removeFromMessageBoxes(key, entry); + return entry; + } + return null; + } + + @UnsupportedAppUsage + @Override + synchronized public void purgeAll() { + super.purgeAll(); + + mMessageBoxes.clear(); + mThreads.clear(); + mUpdating.clear(); + } + + /** + * @param uri The Uri to be normalized. + * @return Uri The normalized key of cached entry. + */ + private Uri normalizeKey(Uri uri) { + int match = URI_MATCHER.match(uri); + Uri normalizedKey = null; + + switch (match) { + case MMS_ALL_ID: + normalizedKey = uri; + break; + case MMS_INBOX_ID: + case MMS_SENT_ID: + case MMS_DRAFTS_ID: + case MMS_OUTBOX_ID: + String msgId = uri.getLastPathSegment(); + normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId); + break; + default: + return null; + } + + if (LOCAL_LOGV) { + Log.v(TAG, uri + " -> " + normalizedKey); + } + return normalizedKey; + } + + private void purgeByMessageBox(Integer msgBoxId) { + if (LOCAL_LOGV) { + Log.v(TAG, "Purge cache in message box: " + msgBoxId); + } + + if (msgBoxId != null) { + HashSet msgBox = mMessageBoxes.remove(msgBoxId); + if (msgBox != null) { + for (Uri key : msgBox) { + mUpdating.remove(key); + PduCacheEntry entry = super.purge(key); + if (entry != null) { + removeFromThreads(key, entry); + } + } + } + } + } + + private void removeFromThreads(Uri key, PduCacheEntry entry) { + HashSet thread = mThreads.get(entry.getThreadId()); + if (thread != null) { + thread.remove(key); + } + } + + private void purgeByThreadId(long threadId) { + if (LOCAL_LOGV) { + Log.v(TAG, "Purge cache in thread: " + threadId); + } + + HashSet thread = mThreads.remove(threadId); + if (thread != null) { + for (Uri key : thread) { + mUpdating.remove(key); + PduCacheEntry entry = super.purge(key); + if (entry != null) { + removeFromMessageBoxes(key, entry); + } + } + } + } + + private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) { + HashSet msgBox = mThreads.get(Long.valueOf(entry.getMessageBox())); + if (msgBox != null) { + msgBox.remove(key); + } + } +} diff --git a/telephony/common/com/google/android/mms/util/PduCacheEntry.java b/telephony/common/com/google/android/mms/util/PduCacheEntry.java new file mode 100644 index 000000000000..1ecd1bf93e7f --- /dev/null +++ b/telephony/common/com/google/android/mms/util/PduCacheEntry.java @@ -0,0 +1,50 @@ +/* + * 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.google.android.mms.util; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import com.google.android.mms.pdu.GenericPdu; + +public final class PduCacheEntry { + private final GenericPdu mPdu; + private final int mMessageBox; + private final long mThreadId; + + @UnsupportedAppUsage + public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) { + mPdu = pdu; + mMessageBox = msgBox; + mThreadId = threadId; + } + + @UnsupportedAppUsage + public GenericPdu getPdu() { + return mPdu; + } + + @UnsupportedAppUsage + public int getMessageBox() { + return mMessageBox; + } + + @UnsupportedAppUsage + public long getThreadId() { + return mThreadId; + } +} diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java new file mode 100644 index 000000000000..2dd1dc11c2a9 --- /dev/null +++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java @@ -0,0 +1,128 @@ +/* + * 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.google.android.mms.util; + +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.util.Log; +import android.widget.Toast; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +public final class SqliteWrapper { + private static final String TAG = "SqliteWrapper"; + private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE + = "unable to open database file"; + + private SqliteWrapper() { + // Forbidden being instantiated. + } + + // FIXME: It looks like outInfo.lowMemory does not work well as we expected. + // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false. + private static boolean isLowMemory(Context context) { + if (null == context) { + return false; + } + + ActivityManager am = (ActivityManager) + context.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo(); + am.getMemoryInfo(outInfo); + + return outInfo.lowMemory; + } + + // FIXME: need to optimize this method. + private static boolean isLowMemory(SQLiteException e) { + return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE); + } + + @UnsupportedAppUsage + public static void checkSQLiteException(Context context, SQLiteException e) { + if (isLowMemory(e)) { + Toast.makeText(context, com.android.internal.R.string.low_memory, + Toast.LENGTH_SHORT).show(); + } else { + throw e; + } + } + + @UnsupportedAppUsage + public static Cursor query(Context context, ContentResolver resolver, Uri uri, + String[] projection, String selection, String[] selectionArgs, String sortOrder) { + try { + return resolver.query(uri, projection, selection, selectionArgs, sortOrder); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when query: ", e); + checkSQLiteException(context, e); + return null; + } + } + + @UnsupportedAppUsage + public static boolean requery(Context context, Cursor cursor) { + try { + return cursor.requery(); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when requery: ", e); + checkSQLiteException(context, e); + return false; + } + } + @UnsupportedAppUsage + public static int update(Context context, ContentResolver resolver, Uri uri, + ContentValues values, String where, String[] selectionArgs) { + try { + return resolver.update(uri, values, where, selectionArgs); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when update: ", e); + checkSQLiteException(context, e); + return -1; + } + } + + @UnsupportedAppUsage + public static int delete(Context context, ContentResolver resolver, Uri uri, + String where, String[] selectionArgs) { + try { + return resolver.delete(uri, where, selectionArgs); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when delete: ", e); + checkSQLiteException(context, e); + return -1; + } + } + + @UnsupportedAppUsage + public static Uri insert(Context context, ContentResolver resolver, + Uri uri, ContentValues values) { + try { + return resolver.insert(uri, values); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when insert: ", e); + checkSQLiteException(context, e); + return null; + } + } +} diff --git a/telephony/common/com/google/android/mms/util/package.html b/telephony/common/com/google/android/mms/util/package.html new file mode 100755 index 000000000000..c9f96a66ab3b --- /dev/null +++ b/telephony/common/com/google/android/mms/util/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + -- cgit v1.2.3 From 24ade16aa0aec4f412e4a40286114fb06470d163 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Thu, 3 Oct 2019 15:24:53 -0700 Subject: Move SmsNumberUtils from opt/telephony to base/telephony/common. This is because MmsService uses SmsNumberUtils. Test: basic sanity Bug: 140763963 Change-Id: I14555c2a641a7ad1a5ca7608d8fa05fa34bbb0f8 --- .../android/internal/telephony/HbpcdLookup.java | 124 ++++ .../com/android/internal/telephony/HbpcdUtils.java | 163 ++++++ .../android/internal/telephony/SmsNumberUtils.java | 627 +++++++++++++++++++++ 3 files changed, 914 insertions(+) create mode 100644 telephony/common/com/android/internal/telephony/HbpcdLookup.java create mode 100644 telephony/common/com/android/internal/telephony/HbpcdUtils.java create mode 100644 telephony/common/com/android/internal/telephony/SmsNumberUtils.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/HbpcdLookup.java b/telephony/common/com/android/internal/telephony/HbpcdLookup.java new file mode 100644 index 000000000000..d9a3e725b6fb --- /dev/null +++ b/telephony/common/com/android/internal/telephony/HbpcdLookup.java @@ -0,0 +1,124 @@ +/* +** +** Copyright 2014, 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.internal.telephony; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * @hide + */ +public class HbpcdLookup { + public static final String AUTHORITY = "hbpcd_lookup"; + + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY); + + public static final String PATH_MCC_IDD = "idd"; + public static final String PATH_MCC_LOOKUP_TABLE = "lookup"; + public static final String PATH_MCC_SID_CONFLICT = "conflict"; + public static final String PATH_MCC_SID_RANGE = "range"; + public static final String PATH_NANP_AREA_CODE = "nanp"; + public static final String PATH_ARBITRARY_MCC_SID_MATCH = "arbitrary"; + public static final String PATH_USERADD_COUNTRY = "useradd"; + + public static final String ID = "_id"; + public static final int IDINDEX = 0; + + /** + * @hide + */ + public static class MccIdd implements BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_IDD); + public static final String DEFAULT_SORT_ORDER = "MCC ASC"; + + public static final String MCC = "MCC"; + public static final String IDD = "IDD"; + + } + + /** + * @hide + */ + public static class MccLookup implements BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_LOOKUP_TABLE); + public static final String DEFAULT_SORT_ORDER = "MCC ASC"; + + public static final String MCC = "MCC"; + public static final String COUNTRY_CODE = "Country_Code"; + public static final String COUNTRY_NAME = "Country_Name"; + public static final String NDD = "NDD"; + public static final String NANPS = "NANPS"; + public static final String GMT_OFFSET_LOW = "GMT_Offset_Low"; + public static final String GMT_OFFSET_HIGH = "GMT_Offset_High"; + public static final String GMT_DST_LOW = "GMT_DST_Low"; + public static final String GMT_DST_HIGH = "GMT_DST_High"; + + } + + /** + * @hide + */ + public static class MccSidConflicts implements BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_SID_CONFLICT); + public static final String DEFAULT_SORT_ORDER = "MCC ASC"; + + public static final String MCC = "MCC"; + public static final String SID_CONFLICT = "SID_Conflict"; + + } + + /** + * @hide + */ + public static class MccSidRange implements BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_SID_RANGE); + public static final String DEFAULT_SORT_ORDER = "MCC ASC"; + + public static final String MCC = "MCC"; + public static final String RANGE_LOW = "SID_Range_Low"; + public static final String RANGE_HIGH = "SID_Range_High"; + } + + /** + * @hide + */ + public static class ArbitraryMccSidMatch implements BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/" + PATH_ARBITRARY_MCC_SID_MATCH); + public static final String DEFAULT_SORT_ORDER = "MCC ASC"; + + public static final String MCC = "MCC"; + public static final String SID = "SID"; + + } + + /** + * @hide + */ + public static class NanpAreaCode implements BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/" + PATH_NANP_AREA_CODE); + public static final String DEFAULT_SORT_ORDER = "Area_Code ASC"; + + public static final String AREA_CODE = "Area_Code"; + } +} diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java new file mode 100644 index 000000000000..2f3194214be6 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014 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.internal.telephony; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.telephony.Rlog; + +import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; +import com.android.internal.telephony.HbpcdLookup.MccIdd; +import com.android.internal.telephony.HbpcdLookup.MccLookup; +import com.android.internal.telephony.HbpcdLookup.MccSidConflicts; +import com.android.internal.telephony.HbpcdLookup.MccSidRange; + +public final class HbpcdUtils { + private static final String LOG_TAG = "HbpcdUtils"; + private static final boolean DBG = false; + private ContentResolver resolver = null; + + public HbpcdUtils(Context context) { + resolver = context.getContentResolver(); + } + + /** + * Resolves the unknown MCC with SID and Timezone information. + */ + public int getMcc(int sid, int tz, int DSTflag, boolean isNitzTimeZone) { + int tmpMcc = 0; + + // check if SID exists in arbitrary_mcc_sid_match table. + // these SIDs are assigned to more than 1 operators, but they are known to + // be used by a specific operator, other operators having the same SID are + // not using it currently, if that SID is in this table, we don't need to + // check other tables. + String projection2[] = {ArbitraryMccSidMatch.MCC}; + Cursor c2 = resolver.query(ArbitraryMccSidMatch.CONTENT_URI, projection2, + ArbitraryMccSidMatch.SID + "=" + sid, null, null); + + if (c2 != null) { + int c2Counter = c2.getCount(); + if (DBG) { + Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); + } + if (c2Counter == 1) { + if (DBG) { + Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); + } + c2.moveToFirst(); + tmpMcc = c2.getInt(0); + if (DBG) { + Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); + } + c2.close(); + return tmpMcc; + } + c2.close(); + } + + // Then check if SID exists in mcc_sid_conflict table. + // and use the timezone in mcc_lookup table to check which MCC matches. + String projection3[] = {MccSidConflicts.MCC}; + Cursor c3 = resolver.query(MccSidConflicts.CONTENT_URI, projection3, + MccSidConflicts.SID_CONFLICT + "=" + sid + " and (((" + + MccLookup.GMT_OFFSET_LOW + "<=" + tz + ") and (" + tz + "<=" + + MccLookup.GMT_OFFSET_HIGH + ") and (" + "0=" + DSTflag + ")) or ((" + + MccLookup.GMT_DST_LOW + "<=" + tz + ") and (" + tz + "<=" + + MccLookup.GMT_DST_HIGH + ") and (" + "1=" + DSTflag + ")))", + null, null); + if (c3 != null) { + int c3Counter = c3.getCount(); + if (c3Counter > 0) { + if (c3Counter > 1) { + Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); + } + if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); + c3.moveToFirst(); + tmpMcc = c3.getInt(0); + if (DBG) { + Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); + } + if (!isNitzTimeZone) { + // time zone is not accurate, it may get wrong mcc, ignore it. + if (DBG) { + Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); + } + tmpMcc = 0; + } + c3.close(); + return tmpMcc; + } else { + c3.close(); + } + } + + // if there is no conflict, then check if SID is in mcc_sid_range. + String projection5[] = {MccSidRange.MCC}; + Cursor c5 = resolver.query(MccSidRange.CONTENT_URI, projection5, + MccSidRange.RANGE_LOW + "<=" + sid + " and " + + MccSidRange.RANGE_HIGH + ">=" + sid, + null, null); + if (c5 != null) { + if (c5.getCount() > 0) { + if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5); + c5.moveToFirst(); + tmpMcc = c5.getInt(0); + if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); + c5.close(); + return tmpMcc; + } + c5.close(); + } + if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range."); + + if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); + // If unknown MCC still could not be resolved, + return tmpMcc; + } + + /** + * Gets country information with given MCC. + */ + public String getIddByMcc(int mcc) { + if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); + String idd = ""; + + Cursor c = null; + + String projection[] = {MccIdd.IDD}; + Cursor cur = resolver.query(MccIdd.CONTENT_URI, projection, + MccIdd.MCC + "=" + mcc, null, null); + if (cur != null) { + if (cur.getCount() > 0) { + if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur); + // TODO: for those country having more than 1 IDDs, need more information + // to decide which IDD would be used. currently just use the first 1. + cur.moveToFirst(); + idd = cur.getString(0); + if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd); + + } + cur.close(); + } + if (c != null) c.close(); + + if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); + return idd; + } +} diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java new file mode 100644 index 000000000000..0d33af639113 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2014 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.internal.telephony; + +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.os.Binder; +import android.os.Build; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.PhoneNumberUtils; +import android.telephony.Rlog; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import com.android.internal.telephony.HbpcdLookup.MccIdd; +import com.android.internal.telephony.HbpcdLookup.MccLookup; + +import java.util.ArrayList; +import java.util.HashMap; + + +/** + * This class implements handle the MO SMS target address before sending. + * This is special for VZW requirement. Follow the specifications of assisted dialing + * of MO SMS while traveling on VZW CDMA, international CDMA or GSM markets. + * {@hide} + */ +public class SmsNumberUtils { + private static final String TAG = "SmsNumberUtils"; + private static final boolean DBG = Build.IS_DEBUGGABLE; + + private static final String PLUS_SIGN = "+"; + + private static final int NANP_SHORT_LENGTH = 7; + private static final int NANP_MEDIUM_LENGTH = 10; + private static final int NANP_LONG_LENGTH = 11; + + private static final int NANP_CC = 1; + private static final String NANP_NDD = "1"; + private static final String NANP_IDD = "011"; + + private static final int MIN_COUNTRY_AREA_LOCAL_LENGTH = 10; + + private static final int GSM_UMTS_NETWORK = 0; + private static final int CDMA_HOME_NETWORK = 1; + private static final int CDMA_ROAMING_NETWORK = 2; + + private static final int NP_NONE = 0; + private static final int NP_NANP_BEGIN = 1; + + /* , - N[2-9] */ + private static final int NP_NANP_LOCAL = NP_NANP_BEGIN; + + /* -, -- N[2-9] */ + private static final int NP_NANP_AREA_LOCAL = NP_NANP_BEGIN + 1; + + /* <1>--, 1--- N[2-9] */ + private static final int NP_NANP_NDD_AREA_LOCAL = NP_NANP_BEGIN + 2; + + /* <+>, +1--- N[2-9] */ + private static final int NP_NANP_NBPCD_CC_AREA_LOCAL = NP_NANP_BEGIN + 3; + + /* , 001-1--- N[2-9] */ + private static final int NP_NANP_LOCALIDD_CC_AREA_LOCAL = NP_NANP_BEGIN + 4; + + /* <+>, +011-1--- N[2-9] */ + private static final int NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL = NP_NANP_BEGIN + 5; + + private static final int NP_INTERNATIONAL_BEGIN = 100; + /* <+>----, +011-86-25-86281234 */ + private static final int NP_NBPCD_HOMEIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN; + + /* ---, 011-86-25-86281234 */ + private static final int NP_HOMEIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 1; + + /* ---, +1-86-25-86281234 */ + private static final int NP_NBPCD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 2; + + /* ---, 00-86-25-86281234 */ + private static final int NP_LOCALIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 3; + + /* --, 86-25-86281234*/ + private static final int NP_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 4; + + private static int[] ALL_COUNTRY_CODES = null; + private static int MAX_COUNTRY_CODES_LENGTH; + private static HashMap> IDDS_MAPS = + new HashMap>(); + + private static class NumberEntry { + public String number; + public String IDD; + public int countryCode; + public NumberEntry(String number) { + this.number = number; + } + } + + /** + * Breaks the given number down and formats it according to the rules + * for different number plans and different network. + * + * @param number destination number which need to be format + * @param activeMcc current network's mcc + * @param networkType current network type + * + * @return the number after formatting. + */ + private static String formatNumber(Context context, String number, + String activeMcc, + int networkType) { + if (number == null ) { + throw new IllegalArgumentException("number is null"); + } + + if (activeMcc == null || activeMcc.trim().length() == 0) { + throw new IllegalArgumentException("activeMcc is null or empty!"); + } + + String networkPortionNumber = PhoneNumberUtils.extractNetworkPortion(number); + if (networkPortionNumber == null || networkPortionNumber.length() == 0) { + throw new IllegalArgumentException("Number is invalid!"); + } + + NumberEntry numberEntry = new NumberEntry(networkPortionNumber); + ArrayList allIDDs = getAllIDDs(context, activeMcc); + + // First check whether the number is a NANP number. + int nanpState = checkNANP(numberEntry, allIDDs); + if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); + + if ((nanpState == NP_NANP_LOCAL) + || (nanpState == NP_NANP_AREA_LOCAL) + || (nanpState == NP_NANP_NDD_AREA_LOCAL)) { + return networkPortionNumber; + } else if (nanpState == NP_NANP_NBPCD_CC_AREA_LOCAL) { + if (networkType == CDMA_HOME_NETWORK + || networkType == CDMA_ROAMING_NETWORK) { + // Remove "+" + return networkPortionNumber.substring(1); + } else { + return networkPortionNumber; + } + } else if (nanpState == NP_NANP_LOCALIDD_CC_AREA_LOCAL) { + if (networkType == CDMA_HOME_NETWORK) { + return networkPortionNumber; + } else if (networkType == GSM_UMTS_NETWORK) { + // Remove the local IDD and replace with "+" + int iddLength = numberEntry.IDD != null ? numberEntry.IDD.length() : 0; + return PLUS_SIGN + networkPortionNumber.substring(iddLength); + } else if (networkType == CDMA_ROAMING_NETWORK) { + // Remove the local IDD + int iddLength = numberEntry.IDD != null ? numberEntry.IDD.length() : 0; + return networkPortionNumber.substring(iddLength); + } + } + + int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs, + NANP_IDD); + if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState)); + String returnNumber = null; + + switch (internationalState) { + case NP_NBPCD_HOMEIDD_CC_AREA_LOCAL: + if (networkType == GSM_UMTS_NETWORK) { + // Remove "+" + returnNumber = networkPortionNumber.substring(1); + } + break; + + case NP_NBPCD_CC_AREA_LOCAL: + // Replace "+" with "011" + returnNumber = NANP_IDD + networkPortionNumber.substring(1); + break; + + case NP_LOCALIDD_CC_AREA_LOCAL: + if (networkType == GSM_UMTS_NETWORK || networkType == CDMA_ROAMING_NETWORK) { + int iddLength = numberEntry.IDD != null ? numberEntry.IDD.length() : 0; + // Replace to ("011") + returnNumber = NANP_IDD + networkPortionNumber.substring(iddLength); + } + break; + + case NP_CC_AREA_LOCAL: + int countryCode = numberEntry.countryCode; + + if (!inExceptionListForNpCcAreaLocal(numberEntry) + && networkPortionNumber.length() >= 11 && countryCode != NANP_CC) { + // Add "011" + returnNumber = NANP_IDD + networkPortionNumber; + } + break; + + case NP_HOMEIDD_CC_AREA_LOCAL: + returnNumber = networkPortionNumber; + break; + + default: + // Replace "+" with 011 in CDMA network if the number's country + // code is not in the HbpcdLookup database. + if (networkPortionNumber.startsWith(PLUS_SIGN) + && (networkType == CDMA_HOME_NETWORK || networkType == CDMA_ROAMING_NETWORK)) { + if (networkPortionNumber.startsWith(PLUS_SIGN + NANP_IDD)) { + // Only remove "+" + returnNumber = networkPortionNumber.substring(1); + } else { + // Replace "+" with "011" + returnNumber = NANP_IDD + networkPortionNumber.substring(1); + } + } + } + + if (returnNumber == null) { + returnNumber = networkPortionNumber; + } + return returnNumber; + } + + /** + * Query International direct dialing from HbpcdLookup.db + * for specified country code + * + * @param mcc current network's country code + * + * @return the IDD array list. + */ + private static ArrayList getAllIDDs(Context context, String mcc) { + ArrayList allIDDs = IDDS_MAPS.get(mcc); + if (allIDDs != null) { + return allIDDs; + } else { + allIDDs = new ArrayList(); + } + + String projection[] = {MccIdd.IDD, MccIdd.MCC}; + String where = null; + + // if mcc is null : return all rows + // if mcc is empty-string : return those rows whose mcc is emptry-string + String[] selectionArgs = null; + if (mcc != null) { + where = MccIdd.MCC + "=?"; + selectionArgs = new String[] {mcc}; + } + + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(MccIdd.CONTENT_URI, projection, + where, selectionArgs, null); + if (cursor.getCount() > 0) { + while (cursor.moveToNext()) { + String idd = cursor.getString(0); + if (!allIDDs.contains(idd)) { + allIDDs.add(idd); + } + } + } + } catch (SQLException e) { + Rlog.e(TAG, "Can't access HbpcdLookup database", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + + IDDS_MAPS.put(mcc, allIDDs); + + if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); + return allIDDs; + } + + + /** + * Verify if the the destination number is a NANP number + * + * @param numberEntry including number and IDD array + * @param allIDDs the IDD array list of the current network's country code + * + * @return the number plan type related NANP + */ + private static int checkNANP(NumberEntry numberEntry, ArrayList allIDDs) { + boolean isNANP = false; + String number = numberEntry.number; + + if (number.length() == NANP_SHORT_LENGTH) { + // 7 digits - Seven digit phone numbers + char firstChar = number.charAt(0); + if (firstChar >= '2' && firstChar <= '9') { + isNANP = true; + for (int i=1; i< NANP_SHORT_LENGTH; i++ ) { + char c= number.charAt(i); + if (!PhoneNumberUtils.isISODigit(c)) { + isNANP = false; + break; + } + } + } + if (isNANP) { + return NP_NANP_LOCAL; + } + } else if (number.length() == NANP_MEDIUM_LENGTH) { + // 10 digits - Three digit area code followed by seven digit phone numbers/ + if (isNANP(number)) { + return NP_NANP_AREA_LOCAL; + } + } else if (number.length() == NANP_LONG_LENGTH) { + // 11 digits - One digit U.S. NDD(National Direct Dial) prefix '1', + // followed by three digit area code and seven digit phone numbers + if (isNANP(number)) { + return NP_NANP_NDD_AREA_LOCAL; + } + } else if (number.startsWith(PLUS_SIGN)) { + number = number.substring(1); + if (number.length() == NANP_LONG_LENGTH) { + // '+' and 11 digits -'+', followed by NANP CC prefix '1' followed by + // three digit area code and seven digit phone numbers + if (isNANP(number)) { + return NP_NANP_NBPCD_CC_AREA_LOCAL; + } + } else if (number.startsWith(NANP_IDD) && number.length() == NANP_LONG_LENGTH + 3) { + // '+' and 14 digits -'+', followed by NANP IDD "011" followed by NANP CC + // prefix '1' followed by three digit area code and seven digit phone numbers + number = number.substring(3); + if (isNANP(number)) { + return NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL; + } + } + } else { + // Check whether it's NP_NANP_LOCALIDD_CC_AREA_LOCAL + for (String idd : allIDDs) { + if (number.startsWith(idd)) { + String number2 = number.substring(idd.length()); + if(number2 !=null && number2.startsWith(String.valueOf(NANP_CC))){ + if (isNANP(number2)) { + numberEntry.IDD = idd; + return NP_NANP_LOCALIDD_CC_AREA_LOCAL; + } + } + } + } + } + + return NP_NONE; + } + + private static boolean isNANP(String number) { + if (number.length() == NANP_MEDIUM_LENGTH + || (number.length() == NANP_LONG_LENGTH && number.startsWith(NANP_NDD))) { + if (number.length() == NANP_LONG_LENGTH) { + number = number.substring(1); + } + return (PhoneNumberUtils.isNanp(number)); + } + return false; + } + + /** + * Verify if the the destination number is an internal number + * + * @param numberEntry including number and IDD array + * @param allIDDs the IDD array list of the current network's country code + * + * @return the number plan type related international number + */ + private static int checkInternationalNumberPlan(Context context, NumberEntry numberEntry, + ArrayList allIDDs,String homeIDD) { + String number = numberEntry.number; + int countryCode = -1; + + if (number.startsWith(PLUS_SIGN)) { + // +xxxxxxxxxx + String numberNoNBPCD = number.substring(1); + if (numberNoNBPCD.startsWith(homeIDD)) { + // +011xxxxxxxx + String numberCountryAreaLocal = numberNoNBPCD.substring(homeIDD.length()); + if ((countryCode = getCountryCode(context, numberCountryAreaLocal)) > 0) { + numberEntry.countryCode = countryCode; + return NP_NBPCD_HOMEIDD_CC_AREA_LOCAL; + } + } else if ((countryCode = getCountryCode(context, numberNoNBPCD)) > 0) { + numberEntry.countryCode = countryCode; + return NP_NBPCD_CC_AREA_LOCAL; + } + + } else if (number.startsWith(homeIDD)) { + // 011xxxxxxxxx + String numberCountryAreaLocal = number.substring(homeIDD.length()); + if ((countryCode = getCountryCode(context, numberCountryAreaLocal)) > 0) { + numberEntry.countryCode = countryCode; + return NP_HOMEIDD_CC_AREA_LOCAL; + } + } else { + for (String exitCode : allIDDs) { + if (number.startsWith(exitCode)) { + String numberNoIDD = number.substring(exitCode.length()); + if ((countryCode = getCountryCode(context, numberNoIDD)) > 0) { + numberEntry.countryCode = countryCode; + numberEntry.IDD = exitCode; + return NP_LOCALIDD_CC_AREA_LOCAL; + } + } + } + + if (!number.startsWith("0") && (countryCode = getCountryCode(context, number)) > 0) { + numberEntry.countryCode = countryCode; + return NP_CC_AREA_LOCAL; + } + } + return NP_NONE; + } + + /** + * Returns the country code from the given number. + */ + private static int getCountryCode(Context context, String number) { + int countryCode = -1; + if (number.length() >= MIN_COUNTRY_AREA_LOCAL_LENGTH) { + // Check Country code + int[] allCCs = getAllCountryCodes(context); + if (allCCs == null) { + return countryCode; + } + + int[] ccArray = new int[MAX_COUNTRY_CODES_LENGTH]; + for (int i = 0; i < MAX_COUNTRY_CODES_LENGTH; i ++) { + ccArray[i] = Integer.parseInt(number.substring(0, i+1)); + } + + for (int i = 0; i < allCCs.length; i ++) { + int tempCC = allCCs[i]; + for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) { + if (tempCC == ccArray[j]) { + if (DBG) Rlog.d(TAG, "Country code = " + tempCC); + return tempCC; + } + } + } + } + + return countryCode; + } + + /** + * Gets all country Codes information with given MCC. + */ + private static int[] getAllCountryCodes(Context context) { + if (ALL_COUNTRY_CODES != null) { + return ALL_COUNTRY_CODES; + } + + Cursor cursor = null; + try { + String projection[] = {MccLookup.COUNTRY_CODE}; + cursor = context.getContentResolver().query(MccLookup.CONTENT_URI, + projection, null, null, null); + + if (cursor.getCount() > 0) { + ALL_COUNTRY_CODES = new int[cursor.getCount()]; + int i = 0; + while (cursor.moveToNext()) { + int countryCode = cursor.getInt(0); + ALL_COUNTRY_CODES[i++] = countryCode; + int length = String.valueOf(countryCode).trim().length(); + if (length > MAX_COUNTRY_CODES_LENGTH) { + MAX_COUNTRY_CODES_LENGTH = length; + } + } + } + } catch (SQLException e) { + Rlog.e(TAG, "Can't access HbpcdLookup database", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return ALL_COUNTRY_CODES; + } + + private static boolean inExceptionListForNpCcAreaLocal(NumberEntry numberEntry) { + int countryCode = numberEntry.countryCode; + boolean result = (numberEntry.number.length() == 12 + && (countryCode == 7 || countryCode == 20 + || countryCode == 65 || countryCode == 90)); + return result; + } + + private static String getNumberPlanType(int state) { + String numberPlanType = "Number Plan type (" + state + "): "; + + if (state == NP_NANP_LOCAL) { + numberPlanType = "NP_NANP_LOCAL"; + } else if (state == NP_NANP_AREA_LOCAL) { + numberPlanType = "NP_NANP_AREA_LOCAL"; + } else if (state == NP_NANP_NDD_AREA_LOCAL) { + numberPlanType = "NP_NANP_NDD_AREA_LOCAL"; + } else if (state == NP_NANP_NBPCD_CC_AREA_LOCAL) { + numberPlanType = "NP_NANP_NBPCD_CC_AREA_LOCAL"; + } else if (state == NP_NANP_LOCALIDD_CC_AREA_LOCAL) { + numberPlanType = "NP_NANP_LOCALIDD_CC_AREA_LOCAL"; + } else if (state == NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL) { + numberPlanType = "NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL"; + } else if (state == NP_NBPCD_HOMEIDD_CC_AREA_LOCAL) { + numberPlanType = "NP_NBPCD_HOMEIDD_CC_AREA_LOCAL"; + } else if (state == NP_HOMEIDD_CC_AREA_LOCAL) { + numberPlanType = "NP_HOMEIDD_CC_AREA_LOCAL"; + } else if (state == NP_NBPCD_CC_AREA_LOCAL) { + numberPlanType = "NP_NBPCD_CC_AREA_LOCAL"; + } else if (state == NP_LOCALIDD_CC_AREA_LOCAL) { + numberPlanType = "NP_LOCALIDD_CC_AREA_LOCAL"; + } else if (state == NP_CC_AREA_LOCAL) { + numberPlanType = "NP_CC_AREA_LOCAL"; + } else { + numberPlanType = "Unknown type"; + } + return numberPlanType; + } + + /** + * Filter the destination number if using VZW sim card. + */ + public static String filterDestAddr(Context context, int subId, String destAddr) { + if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" ); + + if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) { + Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) + + " is not a global phone number! Nothing changed."); + return destAddr; + } + + final TelephonyManager telephonyManager = ((TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE)).createForSubscriptionId(subId); + final String networkOperator = telephonyManager.getNetworkOperator(); + String result = null; + + if (needToConvert(context, subId)) { + final int networkType = getNetworkType(telephonyManager); + if (networkType != -1 && !TextUtils.isEmpty(networkOperator)) { + String networkMcc = networkOperator.substring(0, 3); + if (networkMcc != null && networkMcc.trim().length() > 0) { + result = formatNumber(context, destAddr, networkMcc, networkType); + } + } + } + + if (DBG) { + Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); + Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG, + result) : Rlog.pii(TAG, destAddr)) + "\""); + } + return result != null ? result : destAddr; + } + + /** + * Returns the current network type + */ + private static int getNetworkType(TelephonyManager telephonyManager) { + int networkType = -1; + int phoneType = telephonyManager.getPhoneType(); + + if (phoneType == PhoneConstants.PHONE_TYPE_GSM) { + networkType = GSM_UMTS_NETWORK; + } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { + if (isInternationalRoaming(telephonyManager)) { + networkType = CDMA_ROAMING_NETWORK; + } else { + networkType = CDMA_HOME_NETWORK; + } + } else { + if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType); + } + + return networkType; + } + + private static boolean isInternationalRoaming(TelephonyManager telephonyManager) { + String operatorIsoCountry = telephonyManager.getNetworkCountryIso(); + String simIsoCountry = telephonyManager.getSimCountryIso(); + boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoCountry) + && !TextUtils.isEmpty(simIsoCountry) + && !simIsoCountry.equals(operatorIsoCountry); + if (internationalRoaming) { + if ("us".equals(simIsoCountry)) { + internationalRoaming = !"vi".equals(operatorIsoCountry); + } else if ("vi".equals(simIsoCountry)) { + internationalRoaming = !"us".equals(operatorIsoCountry); + } + } + return internationalRoaming; + } + + private static boolean needToConvert(Context context, int subId) { + // Calling package may not have READ_PHONE_STATE which is required for getConfig(). + // Clear the calling identity so that it is called as self. + final long identity = Binder.clearCallingIdentity(); + try { + CarrierConfigManager configManager = (CarrierConfigManager) + context.getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager != null) { + PersistableBundle bundle = configManager.getConfigForSubId(subId); + if (bundle != null) { + return bundle.getBoolean(CarrierConfigManager + .KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + // by default this value is false + return false; + } +} -- cgit v1.2.3 From 1ec2e61e7f4b6475da754abea129cc206275daa6 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Tue, 8 Oct 2019 10:12:30 -0700 Subject: Move SmsApplication to telephony/common. Test: basic sanity Bug: 140763963 Change-Id: I8662c210319523abe64feaf0610af19fd285c8ca --- .../android/internal/telephony/SmsApplication.java | 1077 ++++++++++++++++++++ 1 file changed, 1077 insertions(+) create mode 100644 telephony/common/com/android/internal/telephony/SmsApplication.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java new file mode 100644 index 000000000000..f4eae8ef4b2a --- /dev/null +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -0,0 +1,1077 @@ +/* + * Copyright (C) 2013 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.internal.telephony; + +import android.Manifest.permission; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.role.RoleManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Debug; +import android.os.Process; +import android.os.UserHandle; +import android.provider.Telephony; +import android.provider.Telephony.Sms.Intents; +import android.telephony.Rlog; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.content.PackageMonitor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import dalvik.annotation.compat.UnsupportedAppUsage; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +/** + * Class for managing the primary application that we will deliver SMS/MMS messages to + * + * {@hide} + */ +public final class SmsApplication { + static final String LOG_TAG = "SmsApplication"; + private static final String PHONE_PACKAGE_NAME = "com.android.phone"; + private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth"; + private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service"; + private static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony"; + + private static final String SCHEME_SMS = "sms"; + private static final String SCHEME_SMSTO = "smsto"; + private static final String SCHEME_MMS = "mms"; + private static final String SCHEME_MMSTO = "mmsto"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_MULTIUSER = false; + + private static final int[] DEFAULT_APP_EXCLUSIVE_APPOPS = { + AppOpsManager.OP_READ_SMS, + AppOpsManager.OP_WRITE_SMS, + AppOpsManager.OP_RECEIVE_SMS, + AppOpsManager.OP_RECEIVE_WAP_PUSH, + AppOpsManager.OP_SEND_SMS, + AppOpsManager.OP_READ_CELL_BROADCASTS + }; + + private static SmsPackageMonitor sSmsPackageMonitor = null; + + public static class SmsApplicationData { + /** + * Name of this SMS app for display. + */ + @UnsupportedAppUsage + private String mApplicationName; + + /** + * Package name for this SMS app. + */ + public String mPackageName; + + /** + * The class name of the SMS_DELIVER_ACTION receiver in this app. + */ + private String mSmsReceiverClass; + + /** + * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app. + */ + private String mMmsReceiverClass; + + /** + * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app. + */ + private String mRespondViaMessageClass; + + /** + * The class name of the ACTION_SENDTO intent in this app. + */ + private String mSendToClass; + + /** + * The class name of the ACTION_DEFAULT_SMS_PACKAGE_CHANGED receiver in this app. + */ + private String mSmsAppChangedReceiverClass; + + /** + * The class name of the ACTION_EXTERNAL_PROVIDER_CHANGE receiver in this app. + */ + private String mProviderChangedReceiverClass; + + /** + * The class name of the SIM_FULL_ACTION receiver in this app. + */ + private String mSimFullReceiverClass; + + /** + * The user-id for this application + */ + private int mUid; + + /** + * Returns true if this SmsApplicationData is complete (all intents handled). + * @return + */ + public boolean isComplete() { + return (mSmsReceiverClass != null && mMmsReceiverClass != null + && mRespondViaMessageClass != null && mSendToClass != null); + } + + public SmsApplicationData(String packageName, int uid) { + mPackageName = packageName; + mUid = uid; + } + + public String getApplicationName(Context context) { + if (mApplicationName == null) { + PackageManager pm = context.getPackageManager(); + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfoAsUser(mPackageName, 0, + UserHandle.getUserHandleForUid(mUid)); + } catch (NameNotFoundException e) { + return null; + } + if (appInfo != null) { + CharSequence label = pm.getApplicationLabel(appInfo); + mApplicationName = (label == null) ? null : label.toString(); + } + } + return mApplicationName; + } + + @Override + public String toString() { + return " mPackageName: " + mPackageName + + " mSmsReceiverClass: " + mSmsReceiverClass + + " mMmsReceiverClass: " + mMmsReceiverClass + + " mRespondViaMessageClass: " + mRespondViaMessageClass + + " mSendToClass: " + mSendToClass + + " mSmsAppChangedClass: " + mSmsAppChangedReceiverClass + + " mProviderChangedReceiverClass: " + mProviderChangedReceiverClass + + " mSimFullReceiverClass: " + mSimFullReceiverClass + + " mUid: " + mUid; + } + } + + /** + * Returns the userId of the Context object, if called from a system app, + * otherwise it returns the caller's userId + * @param context The context object passed in by the caller. + * @return + */ + private static int getIncomingUserId(Context context) { + int contextUserId = UserHandle.myUserId(); + final int callingUid = Binder.getCallingUid(); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid=" + + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4)); + } + if (UserHandle.getAppId(callingUid) + < android.os.Process.FIRST_APPLICATION_UID) { + return contextUserId; + } else { + return UserHandle.getUserId(callingUid); + } + } + + /** + * Returns the list of available SMS apps defined as apps that are registered for both the + * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast + * receivers are enabled) + * + * Requirements to be an SMS application: + * Implement SMS_DELIVER_ACTION broadcast receiver. + * Require BROADCAST_SMS permission. + * + * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver. + * Require BROADCAST_WAP_PUSH permission. + * + * Implement RESPOND_VIA_MESSAGE intent. + * Support smsto Uri scheme. + * Require SEND_RESPOND_VIA_MESSAGE permission. + * + * Implement ACTION_SENDTO intent. + * Support smsto Uri scheme. + */ + @UnsupportedAppUsage + public static Collection getApplicationCollection(Context context) { + return getApplicationCollectionAsUser(context, getIncomingUserId(context)); + } + + /** + * Same as {@link #getApplicationCollection} but it takes a target user ID. + */ + public static Collection getApplicationCollectionAsUser(Context context, + int userId) { + final long token = Binder.clearCallingIdentity(); + try { + return getApplicationCollectionInternal(context, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private static Collection getApplicationCollectionInternal( + Context context, int userId) { + PackageManager packageManager = context.getPackageManager(); + + // Get the list of apps registered for SMS + Intent intent = new Intent(Intents.SMS_DELIVER_ACTION); + if (DEBUG) { + intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); + } + List smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); + + HashMap receivers = new HashMap(); + + // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers) + for (ResolveInfo resolveInfo : smsReceivers) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) { + continue; + } + final String packageName = activityInfo.packageName; + if (!receivers.containsKey(packageName)) { + final SmsApplicationData smsApplicationData = new SmsApplicationData(packageName, + activityInfo.applicationInfo.uid); + smsApplicationData.mSmsReceiverClass = activityInfo.name; + receivers.put(packageName, smsApplicationData); + } + } + + // Update any existing entries with mms receiver class + intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); + intent.setDataAndType(null, "application/vnd.wap.mms-message"); + List mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); + for (ResolveInfo resolveInfo : mmsReceivers) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) { + continue; + } + final String packageName = activityInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (smsApplicationData != null) { + smsApplicationData.mMmsReceiverClass = activityInfo.name; + } + } + + // Update any existing entries with respond via message intent class. + intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, + Uri.fromParts(SCHEME_SMSTO, "", null)); + List respondServices = packageManager.queryIntentServicesAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + UserHandle.of(userId)); + for (ResolveInfo resolveInfo : respondServices) { + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (serviceInfo == null) { + continue; + } + if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) { + continue; + } + final String packageName = serviceInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (smsApplicationData != null) { + smsApplicationData.mRespondViaMessageClass = serviceInfo.name; + } + } + + // Update any existing entries with supports send to. + intent = new Intent(Intent.ACTION_SENDTO, + Uri.fromParts(SCHEME_SMSTO, "", null)); + List sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); + for (ResolveInfo resolveInfo : sendToActivities) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + final String packageName = activityInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (smsApplicationData != null) { + smsApplicationData.mSendToClass = activityInfo.name; + } + } + + // Update any existing entries with the default sms changed handler. + intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); + List smsAppChangedReceivers = + packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" + + smsAppChangedReceivers); + } + for (ResolveInfo resolveInfo : smsAppChangedReceivers) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + final String packageName = activityInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" + + packageName + " smsApplicationData: " + smsApplicationData + + " activityInfo.name: " + activityInfo.name); + } + if (smsApplicationData != null) { + smsApplicationData.mSmsAppChangedReceiverClass = activityInfo.name; + } + } + + // Update any existing entries with the external provider changed handler. + intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE); + List providerChangedReceivers = + packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" + + providerChangedReceivers); + } + for (ResolveInfo resolveInfo : providerChangedReceivers) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + final String packageName = activityInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" + + packageName + " smsApplicationData: " + smsApplicationData + + " activityInfo.name: " + activityInfo.name); + } + if (smsApplicationData != null) { + smsApplicationData.mProviderChangedReceiverClass = activityInfo.name; + } + } + + // Update any existing entries with the sim full handler. + intent = new Intent(Intents.SIM_FULL_ACTION); + List simFullReceivers = + packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers=" + + simFullReceivers); + } + for (ResolveInfo resolveInfo : simFullReceivers) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + final String packageName = activityInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" + + packageName + " smsApplicationData: " + smsApplicationData + + " activityInfo.name: " + activityInfo.name); + } + if (smsApplicationData != null) { + smsApplicationData.mSimFullReceiverClass = activityInfo.name; + } + } + + // Remove any entries for which we did not find all required intents. + for (ResolveInfo resolveInfo : smsReceivers) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + continue; + } + final String packageName = activityInfo.packageName; + final SmsApplicationData smsApplicationData = receivers.get(packageName); + if (smsApplicationData != null) { + if (!smsApplicationData.isComplete()) { + receivers.remove(packageName); + } + } + } + return receivers.values(); + } + + /** + * Checks to see if we have a valid installed SMS application for the specified package name + * @return Data for the specified package name or null if there isn't one + */ + public static SmsApplicationData getApplicationForPackage( + Collection applications, String packageName) { + if (packageName == null) { + return null; + } + // Is there an entry in the application list for the specified package? + for (SmsApplicationData application : applications) { + if (application.mPackageName.contentEquals(packageName)) { + return application; + } + } + return null; + } + + /** + * Get the application we will use for delivering SMS/MMS messages. + * + * We return the preferred sms application with the following order of preference: + * (1) User selected SMS app (if selected, and if still valid) + * (2) Android Messaging (if installed) + * (3) The currently configured highest priority broadcast receiver + * (4) Null + */ + private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded, + int userId) { + TelephonyManager tm = (TelephonyManager) + context.getSystemService(Context.TELEPHONY_SERVICE); + RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE); + // (b/134400042) RoleManager might be null in unit tests running older mockito versions + // that do not support mocking final classes. + if (!tm.isSmsCapable() && (roleManager == null || !roleManager.isRoleAvailable( + RoleManager.ROLE_SMS))) { + // No phone, no SMS + return null; + } + + Collection applications = getApplicationCollectionInternal(context, + userId); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplication userId=" + userId); + } + // Determine which application receives the broadcast + String defaultApplication = getDefaultSmsPackage(context, userId); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication); + } + + SmsApplicationData applicationData = null; + if (defaultApplication != null) { + applicationData = getApplicationForPackage(applications, defaultApplication); + } + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplication appData=" + applicationData); + } + + // If we found a package, make sure AppOps permissions are set up correctly + if (applicationData != null) { + // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we + // are checking is for our current uid. Doing this check from the unprivileged current + // SMS app allows us to tell the current SMS app that it is not in a good state and + // needs to ask to be the current SMS app again to work properly. + if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) { + // Verify that the SMS app has permissions + boolean appOpsFixed = + tryFixExclusiveSmsAppops(context, applicationData, updateIfNeeded); + if (!appOpsFixed) { + // We can not return a package if permissions are not set up correctly + applicationData = null; + } + } + + // We can only verify the phone and BT app's permissions from a privileged caller + if (applicationData != null && updateIfNeeded) { + // Ensure this component is still configured as the preferred activity. Usually the + // current SMS app will already be the preferred activity - but checking whether or + // not this is true is just as expensive as reconfiguring the preferred activity so + // we just reconfigure every time. + defaultSmsAppChanged(context); + } + } + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "getApplication returning appData=" + applicationData); + } + return applicationData; + } + + private static String getDefaultSmsPackage(Context context, int userId) { + return context.getSystemService(RoleManager.class).getDefaultSmsPackage(userId); + } + + /** + * Grants various permissions and appops on sms app change + */ + private static void defaultSmsAppChanged(Context context) { + PackageManager packageManager = context.getPackageManager(); + AppOpsManager appOps = context.getSystemService(AppOpsManager.class); + + // Assign permission to special system apps + assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, + PHONE_PACKAGE_NAME); + assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, + BLUETOOTH_PACKAGE_NAME); + assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, + MMS_SERVICE_PACKAGE_NAME); + assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, + TELEPHONY_PROVIDER_PACKAGE_NAME); + + // Give AppOps permission to UID 1001 which contains multiple + // apps, all of them should be able to write to telephony provider. + // This is to allow the proxy package permission check in telephony provider + // to pass. + for (int appop : DEFAULT_APP_EXCLUSIVE_APPOPS) { + appOps.setUidMode(appop, Process.PHONE_UID, AppOpsManager.MODE_ALLOWED); + } + } + + private static boolean tryFixExclusiveSmsAppops(Context context, + SmsApplicationData applicationData, boolean updateIfNeeded) { + AppOpsManager appOps = context.getSystemService(AppOpsManager.class); + for (int appOp : DEFAULT_APP_EXCLUSIVE_APPOPS) { + int mode = appOps.checkOp(appOp, applicationData.mUid, + applicationData.mPackageName); + if (mode != AppOpsManager.MODE_ALLOWED) { + Rlog.e(LOG_TAG, applicationData.mPackageName + " lost " + + AppOpsManager.modeToName(appOp) + ": " + + (updateIfNeeded ? " (fixing)" : " (no permission to fix)")); + if (updateIfNeeded) { + appOps.setUidMode(appOp, applicationData.mUid, AppOpsManager.MODE_ALLOWED); + } else { + return false; + } + } + } + return true; + } + + /** + * Sets the specified package as the default SMS/MMS application. The caller of this method + * needs to have permission to set AppOps and write to secure settings. + */ + @UnsupportedAppUsage + public static void setDefaultApplication(String packageName, Context context) { + setDefaultApplicationAsUser(packageName, context, getIncomingUserId(context)); + } + + /** + * Same as {@link #setDefaultApplication} but takes a target user id. + */ + public static void setDefaultApplicationAsUser(String packageName, Context context, + int userId) { + TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE); + // (b/134400042) RoleManager might be null in unit tests running older mockito versions + // that do not support mocking final classes. + if (!tm.isSmsCapable() && (roleManager == null || !roleManager.isRoleAvailable( + RoleManager.ROLE_SMS))) { + // No phone, no SMS + return; + } + + final long token = Binder.clearCallingIdentity(); + try { + setDefaultApplicationInternal(packageName, context, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private static void setDefaultApplicationInternal(String packageName, Context context, + int userId) { + final UserHandle userHandle = UserHandle.of(userId); + + // Get old package name + String oldPackageName = getDefaultSmsPackage(context, userId); + + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName + + " new=" + packageName); + } + + if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) { + // No change + return; + } + + // We only make the change if the new package is valid + PackageManager packageManager = context.getPackageManager(); + Collection applications = getApplicationCollectionInternal( + context, userId); + SmsApplicationData oldAppData = oldPackageName != null ? + getApplicationForPackage(applications, oldPackageName) : null; + SmsApplicationData applicationData = getApplicationForPackage(applications, packageName); + if (applicationData != null) { + // Ignore relevant appops for the previously configured default SMS app. + AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + if (oldPackageName != null) { + try { + int uid = packageManager.getPackageInfoAsUser( + oldPackageName, 0, userId).applicationInfo.uid; + setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT); + } catch (NameNotFoundException e) { + Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); + } + } + + // Update the setting. + CompletableFuture future = new CompletableFuture<>(); + Consumer callback = successful -> { + if (successful) { + future.complete(null); + } else { + future.completeExceptionally(new RuntimeException()); + } + }; + context.getSystemService(RoleManager.class).addRoleHolderAsUser( + RoleManager.ROLE_SMS, applicationData.mPackageName, 0, UserHandle.of(userId), + AsyncTask.THREAD_POOL_EXECUTOR, callback); + try { + future.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Log.e(LOG_TAG, "Exception while adding sms role holder " + applicationData, e); + return; + } + + defaultSmsAppChanged(context); + } + } + + /** + * Sends broadcasts on sms app change: + * {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} + * {@link Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL} + */ + public static void broadcastSmsAppChange(Context context, + UserHandle userHandle, @Nullable String oldPackage, @Nullable String newPackage) { + Collection apps = getApplicationCollection(context); + + broadcastSmsAppChange(context, userHandle, + getApplicationForPackage(apps, oldPackage), + getApplicationForPackage(apps, newPackage)); + } + + private static void broadcastSmsAppChange(Context context, UserHandle userHandle, + @Nullable SmsApplicationData oldAppData, + @Nullable SmsApplicationData applicationData) { + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData); + } + if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) { + // Notify the old sms app that it's no longer the default + final Intent oldAppIntent = + new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); + final ComponentName component = new ComponentName(oldAppData.mPackageName, + oldAppData.mSmsAppChangedReceiverClass); + oldAppIntent.setComponent(component); + oldAppIntent.putExtra(Intents.EXTRA_IS_DEFAULT_SMS_APP, false); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName); + } + context.sendBroadcastAsUser(oldAppIntent, userHandle); + } + // Notify the new sms app that it's now the default (if the new sms app has a receiver + // to handle the changed default sms intent). + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" + + applicationData); + } + if (applicationData != null && applicationData.mSmsAppChangedReceiverClass != null) { + final Intent intent = + new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); + final ComponentName component = new ComponentName(applicationData.mPackageName, + applicationData.mSmsAppChangedReceiverClass); + intent.setComponent(component); + intent.putExtra(Intents.EXTRA_IS_DEFAULT_SMS_APP, true); + if (DEBUG_MULTIUSER) { + Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + applicationData.mPackageName); + } + context.sendBroadcastAsUser(intent, userHandle); + } + + // Send an implicit broadcast for the system server. + // (or anyone with MONITOR_DEFAULT_SMS_PACKAGE, really.) + final Intent intent = + new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); + context.sendBroadcastAsUser(intent, userHandle, + permission.MONITOR_DEFAULT_SMS_PACKAGE); + + if (applicationData != null) { + MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED, + applicationData.mPackageName); + } + } + + /** + * Assign WRITE_SMS AppOps permission to some special system apps. + * + * @param context The context + * @param packageManager The package manager instance + * @param appOps The AppOps manager instance + * @param packageName The package name of the system app + */ + private static void assignExclusiveSmsPermissionsToSystemApp(Context context, + PackageManager packageManager, AppOpsManager appOps, String packageName) { + // First check package signature matches the caller's package signature. + // Since this class is only used internally by the system, this check makes sure + // the package signature matches system signature. + final int result = packageManager.checkSignatures(context.getPackageName(), packageName); + if (result != PackageManager.SIGNATURE_MATCH) { + Rlog.e(LOG_TAG, packageName + " does not have system signature"); + return; + } + try { + PackageInfo info = packageManager.getPackageInfo(packageName, 0); + int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid, + packageName); + if (mode != AppOpsManager.MODE_ALLOWED) { + Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); + setExclusiveAppops(packageName, appOps, info.applicationInfo.uid, + AppOpsManager.MODE_ALLOWED); + } + } catch (NameNotFoundException e) { + // No whitelisted system app on this device + Rlog.e(LOG_TAG, "Package not found: " + packageName); + } + + } + + private static void setExclusiveAppops(String pkg, AppOpsManager appOpsManager, int uid, + int mode) { + for (int appop : DEFAULT_APP_EXCLUSIVE_APPOPS) { + appOpsManager.setUidMode(appop, uid, mode); + } + } + + /** + * Tracks package changes and ensures that the default SMS app is always configured to be the + * preferred activity for SENDTO sms/mms intents. + */ + private static final class SmsPackageMonitor extends PackageMonitor { + final Context mContext; + + public SmsPackageMonitor(Context context) { + super(); + mContext = context; + } + + @Override + public void onPackageDisappeared(String packageName, int reason) { + onPackageChanged(); + } + + @Override + public void onPackageAppeared(String packageName, int reason) { + onPackageChanged(); + } + + @Override + public void onPackageModified(String packageName) { + onPackageChanged(); + } + + private void onPackageChanged() { + PackageManager packageManager = mContext.getPackageManager(); + Context userContext = mContext; + final int userId = getSendingUserId(); + if (userId != UserHandle.USER_SYSTEM) { + try { + userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, + UserHandle.of(userId)); + } catch (NameNotFoundException nnfe) { + if (DEBUG_MULTIUSER) { + Log.w(LOG_TAG, "Unable to create package context for user " + userId); + } + } + } + // Ensure this component is still configured as the preferred activity + ComponentName componentName = getDefaultSendToApplication(userContext, true); + if (componentName != null) { + configurePreferredActivity(packageManager, componentName, userId); + } + } + } + + public static void initSmsPackageMonitor(Context context) { + sSmsPackageMonitor = new SmsPackageMonitor(context); + sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false); + } + + @UnsupportedAppUsage + private static void configurePreferredActivity(PackageManager packageManager, + ComponentName componentName, int userId) { + // Add the four activity preferences we want to direct to this app. + replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS); + replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO); + replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS); + replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO); + } + + /** + * Updates the ACTION_SENDTO activity to the specified component for the specified scheme. + */ + private static void replacePreferredActivity(PackageManager packageManager, + ComponentName componentName, int userId, String scheme) { + // Build the set of existing activities that handle this scheme + Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null)); + List resolveInfoList = packageManager.queryIntentActivitiesAsUser( + intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER, + userId); + + // Build the set of ComponentNames for these activities + final int n = resolveInfoList.size(); + ComponentName[] set = new ComponentName[n]; + for (int i = 0; i < n; i++) { + ResolveInfo info = resolveInfoList.get(i); + set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); + } + + // Update the preferred SENDTO activity for the specified scheme + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_SENDTO); + intentFilter.addCategory(Intent.CATEGORY_DEFAULT); + intentFilter.addDataScheme(scheme); + packageManager.replacePreferredActivityAsUser(intentFilter, + IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL, + set, componentName, userId); + } + + /** + * Returns SmsApplicationData for this package if this package is capable of being set as the + * default SMS application. + */ + @UnsupportedAppUsage + public static SmsApplicationData getSmsApplicationData(String packageName, Context context) { + Collection applications = getApplicationCollection(context); + return getApplicationForPackage(applications, packageName); + } + + /** + * Gets the default SMS application + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to deliver SMS messages to + */ + @UnsupportedAppUsage + public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) { + return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId(context)); + } + + /** + * Gets the default SMS application on a given user + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @param userId target user ID. + * @return component name of the app and class to deliver SMS messages to + */ + public static ComponentName getDefaultSmsApplicationAsUser(Context context, + boolean updateIfNeeded, int userId) { + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mSmsReceiverClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default MMS application + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to deliver MMS messages to + */ + @UnsupportedAppUsage + public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mMmsReceiverClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default Respond Via Message application + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to direct Respond Via Message intent to + */ + @UnsupportedAppUsage + public static ComponentName getDefaultRespondViaMessageApplication(Context context, + boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mRespondViaMessageClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default Send To (smsto) application. + *

+ * Caller must pass in the correct user context if calling from a singleton service. + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to direct SEND_TO (smsto) intent to + */ + public static ComponentName getDefaultSendToApplication(Context context, + boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mSendToClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default application that handles external changes to the SmsProvider and + * MmsProvider. + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to deliver change intents to + */ + public static ComponentName getDefaultExternalTelephonyProviderChangedApplication( + Context context, boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null + && smsApplicationData.mProviderChangedReceiverClass != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mProviderChangedReceiverClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default application that handles sim full event. + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to deliver change intents to + */ + public static ComponentName getDefaultSimFullApplication( + Context context, boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null + && smsApplicationData.mSimFullReceiverClass != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mSimFullReceiverClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Returns whether need to write the SMS message to SMS database for this package. + *

+ * Caller must pass in the correct user context if calling from a singleton service. + */ + @UnsupportedAppUsage + public static boolean shouldWriteMessageForPackage(String packageName, Context context) { + return !isDefaultSmsApplication(context, packageName); + } + + /** + * Check if a package is default sms app (or equivalent, like bluetooth) + * + * @param context context from the calling app + * @param packageName the name of the package to be checked + * @return true if the package is default sms app or bluetooth + */ + @UnsupportedAppUsage + public static boolean isDefaultSmsApplication(Context context, String packageName) { + if (packageName == null) { + return false; + } + final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context); + if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName)) + || BLUETOOTH_PACKAGE_NAME.equals(packageName)) { + return true; + } + return false; + } + + private static String getDefaultSmsApplicationPackageName(Context context) { + final ComponentName component = getDefaultSmsApplication(context, false); + if (component != null) { + return component.getPackageName(); + } + return null; + } +} -- cgit v1.2.3 From ee198a8f3f0c374c58d1832d7423babecae6d16a Mon Sep 17 00:00:00 2001 From: changbetty Date: Wed, 13 Nov 2019 20:21:42 +0800 Subject: [Telephony MainLine] Add PackageChangeReceiver to replace PackageMonitor Bug: 143918416 Test: build pass and device boots up Change-Id: I0539d230fbe17d6f0cac3ba01c1b926db787d5ba --- .../internal/telephony/PackageChangeReceiver.java | 167 +++++++++++++++++++++ .../android/internal/telephony/SmsApplication.java | 10 +- 2 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 telephony/common/com/android/internal/telephony/PackageChangeReceiver.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java new file mode 100644 index 000000000000..922af126e73e --- /dev/null +++ b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 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 android.telephony; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; + +import com.android.internal.os.BackgroundThread; + +/** + * Helper class for monitoring the state of packages: adding, removing, + * updating, and disappearing and reappearing on the SD card. + */ +public abstract class PackageChangeReceiver extends BroadcastReceiver { + static final IntentFilter sPackageIntentFilter = new IntentFilter(); + static { + sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + sPackageIntentFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + sPackageIntentFilter.addDataScheme("package"); + } + Context mRegisteredContext; + + /** + * To register the intents that needed for monitoring the state of packages + */ + public void register(@NonNull Context context, @Nullable Looper thread, + @Nullable UserHandle user) { + if (mRegisteredContext != null) { + throw new IllegalStateException("Already registered"); + } + Handler handler = (thread == null) ? BackgroundThread.getHandler() : new Handler(thread); + mRegisteredContext = context; + if (handler != null) { + if (user != null) { + context.registerReceiverAsUser(this, user, sPackageIntentFilter, null, handler); + } else { + context.registerReceiver(this, sPackageIntentFilter, + null, handler); + } + } else { + throw new NullPointerException(); + } + } + + /** + * To unregister the intents for monitoring the state of packages + */ + public void unregister() { + if (mRegisteredContext == null) { + throw new IllegalStateException("Not registered"); + } + mRegisteredContext.unregisterReceiver(this); + mRegisteredContext = null; + } + + /** + * This method is invoked when receive the Intent.ACTION_PACKAGE_ADDED + */ + public void onPackageAdded(@Nullable String packageName) { + } + + /** + * This method is invoked when receive the Intent.ACTION_PACKAGE_REMOVED + */ + public void onPackageRemoved(@Nullable String packageName) { + } + + /** + * This method is invoked when Intent.EXTRA_REPLACING as extra field is true + */ + public void onPackageUpdateFinished(@Nullable String packageName) { + } + + /** + * This method is invoked when receive the Intent.ACTION_PACKAGE_CHANGED or + * Intent.EXTRA_REPLACING as extra field is true + */ + public void onPackageModified(@Nullable String packageName) { + } + + /** + * This method is invoked when receive the Intent.ACTION_QUERY_PACKAGE_RESTART and + * Intent.ACTION_PACKAGE_RESTARTED + */ + public void onHandleForceStop(@Nullable String[] packages, boolean doit) { + } + + /** + * This method is invoked when receive the Intent.ACTION_PACKAGE_REMOVED + */ + public void onPackageDisappeared() { + } + + /** + * This method is invoked when receive the Intent.ACTION_PACKAGE_ADDED + */ + public void onPackageAppeared() { + } + + @Override + public void onReceive(@Nullable Context context, @Nullable Intent intent) { + String action = intent.getAction(); + + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + String pkg = getPackageName(intent); + if (pkg != null) { + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + onPackageUpdateFinished(pkg); + onPackageModified(pkg); + } else { + onPackageAdded(pkg); + } + onPackageAppeared(); + } + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + String pkg = getPackageName(intent); + if (pkg != null) { + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + onPackageRemoved(pkg); + } + onPackageDisappeared(); + } + } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + String pkg = getPackageName(intent); + if (pkg != null) { + onPackageModified(pkg); + } + } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { + String[] disappearingPackages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + onHandleForceStop(disappearingPackages, false); + } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) { + String[] disappearingPackages = new String[] {getPackageName(intent)}; + onHandleForceStop(disappearingPackages, true); + } + } + + String getPackageName(Intent intent) { + Uri uri = intent.getData(); + String pkg = uri != null ? uri.getSchemeSpecificPart() : null; + return pkg; + } +} diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index f4eae8ef4b2a..70ce1bfc4bff 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -39,11 +39,11 @@ import android.os.Process; import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; +import android.telephony.PackageChangeReceiver; import android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.Log; -import com.android.internal.content.PackageMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -777,7 +777,7 @@ public final class SmsApplication { * Tracks package changes and ensures that the default SMS app is always configured to be the * preferred activity for SENDTO sms/mms intents. */ - private static final class SmsPackageMonitor extends PackageMonitor { + private static final class SmsPackageMonitor extends PackageChangeReceiver { final Context mContext; public SmsPackageMonitor(Context context) { @@ -786,12 +786,12 @@ public final class SmsApplication { } @Override - public void onPackageDisappeared(String packageName, int reason) { + public void onPackageDisappeared() { onPackageChanged(); } @Override - public void onPackageAppeared(String packageName, int reason) { + public void onPackageAppeared() { onPackageChanged(); } @@ -824,7 +824,7 @@ public final class SmsApplication { public static void initSmsPackageMonitor(Context context) { sSmsPackageMonitor = new SmsPackageMonitor(context); - sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false); + sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL); } @UnsupportedAppUsage -- cgit v1.2.3 From 3a7dfca8db0185ce0683e22c11b7906215a96a51 Mon Sep 17 00:00:00 2001 From: Chen Xu Date: Tue, 26 Nov 2019 13:46:41 -0800 Subject: move encodeException to non-updatable framework/base/telephony/common Bug: 140908357 Test: Build Change-Id: I9be6b41fe4af0eb4eb56debffc020735509dc407 --- .../internal/telephony/EncodeException.java | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 telephony/common/com/android/internal/telephony/EncodeException.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/EncodeException.java b/telephony/common/com/android/internal/telephony/EncodeException.java new file mode 100644 index 000000000000..cdc853e09895 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/EncodeException.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2006 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.internal.telephony; + +import android.annotation.UnsupportedAppUsage; + +/** + * {@hide} + */ +public class EncodeException extends Exception { + + private int mError = ERROR_UNENCODABLE; + + public static final int ERROR_UNENCODABLE = 0; + public static final int ERROR_EXCEED_SIZE = 1; + + public EncodeException() { + super(); + } + + @UnsupportedAppUsage + public EncodeException(String s) { + super(s); + } + + public EncodeException(String s, int error) { + super(s); + mError = error; + } + + @UnsupportedAppUsage + public EncodeException(char c) { + super("Unencodable char: '" + c + "'"); + } + + public int getError() { + return mError; + } +} + -- cgit v1.2.3 From fde53bb6f11069b7e4ed2daaf4ac2d65c1d54192 Mon Sep 17 00:00:00 2001 From: Shuo Qian Date: Wed, 4 Dec 2019 02:06:06 +0000 Subject: Move TelephonyPermissions to TelephonyCommon Test: built Bug: 145554073 Change-Id: I3681043a197e6daeb16dcf506d672bdcf1fc8da8 --- .../internal/telephony/TelephonyPermissions.java | 709 +++++++++++++++++++++ 1 file changed, 709 insertions(+) create mode 100644 telephony/common/com/android/internal/telephony/TelephonyPermissions.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java new file mode 100644 index 000000000000..86630b0eb991 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -0,0 +1,709 @@ +/* + * Copyright (C) 2018 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.internal.telephony; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.Manifest; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.util.StatsLog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +/** Utility class for Telephony permission enforcement. */ +public final class TelephonyPermissions { + private static final String LOG_TAG = "TelephonyPermissions"; + + private static final boolean DBG = false; + + private static final Supplier TELEPHONY_SUPPLIER = () -> + ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); + + /** + * Whether to disable the new device identifier access restrictions. + */ + private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED = + "device_identifier_access_restrictions_disabled"; + + // Contains a mapping of packages that did not meet the new requirements to access device + // identifiers and the methods they were attempting to invoke; used to prevent duplicate + // reporting of packages / methods. + private static final Map> sReportedDeviceIDPackages; + static { + sReportedDeviceIDPackages = new HashMap<>(); + } + + private TelephonyPermissions() {} + + /** + * Check whether the caller (or self, if not processing an IPC) can read phone state. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the + * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. + *
  • throw SecurityException: if the caller didn't declare any of these permissions, or, for + * apps which support runtime permissions, if the caller does not currently have any of + * these permissions. + *
  • return false: if the caller lacks all of these permissions and doesn't support runtime + * permissions. This implies that the user revoked the ability to read phone state + * manually (via AppOps). In this case we can't throw as it would break app compatibility, + * so we return false to indicate that the calling function should return dummy data. + *
+ * + *

Note: for simplicity, this method always returns false for callers using legacy + * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. + * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ + * devices. + * + * @param subId the subId of the relevant subscription; used to check carrier privileges. May be + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} to skip this check for cases + * where it isn't relevant (hidden APIs, or APIs which are otherwise okay to leave + * inaccesible to carrier-privileged apps). + */ + public static boolean checkCallingOrSelfReadPhoneState( + Context context, int subId, String callingPackage, @Nullable String callingFeatureId, + String message) { + return checkReadPhoneState(context, subId, Binder.getCallingPid(), Binder.getCallingUid(), + callingPackage, callingFeatureId, message); + } + + /** Identical to checkCallingOrSelfReadPhoneState but never throws SecurityException */ + public static boolean checkCallingOrSelfReadPhoneStateNoThrow( + Context context, int subId, String callingPackage, @Nullable String callingFeatureId, + String message) { + try { + return checkCallingOrSelfReadPhoneState(context, subId, callingPackage, + callingFeatureId, message); + } catch (SecurityException se) { + return false; + } + } + + /** + * Check whether the app with the given pid/uid can read phone state. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the + * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. + *
  • throw SecurityException: if the caller didn't declare any of these permissions, or, for + * apps which support runtime permissions, if the caller does not currently have any of + * these permissions. + *
  • return false: if the caller lacks all of these permissions and doesn't support runtime + * permissions. This implies that the user revoked the ability to read phone state + * manually (via AppOps). In this case we can't throw as it would break app compatibility, + * so we return false to indicate that the calling function should return dummy data. + *
+ * + *

Note: for simplicity, this method always returns false for callers using legacy + * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. + * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ + * devices. + */ + public static boolean checkReadPhoneState( + Context context, int subId, int pid, int uid, String callingPackage, + @Nullable String callingFeatureId, String message) { + return checkReadPhoneState( + context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingFeatureId, + message); + } + + /** + * Check whether the calling packages has carrier privileges for the passing subscription. + * @return {@code true} if the caller has carrier privileges, {@false} otherwise. + */ + public static boolean checkCarrierPrivilegeForSubId(int subId) { + if (SubscriptionManager.isValidSubscriptionId(subId) + && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, Binder.getCallingUid()) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + return false; + } + + /** + * Check whether the app with the given pid/uid can read phone state. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the + * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. + *
  • throw SecurityException: if the caller didn't declare any of these permissions, or, for + * apps which support runtime permissions, if the caller does not currently have any of + * these permissions. + *
  • return false: if the caller lacks all of these permissions and doesn't support runtime + * permissions. This implies that the user revoked the ability to read phone state + * manually (via AppOps). In this case we can't throw as it would break app compatibility, + * so we return false to indicate that the calling function should return dummy data. + *
+ * + *

Note: for simplicity, this method always returns false for callers using legacy + * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. + * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ + * devices. + */ + @VisibleForTesting + public static boolean checkReadPhoneState( + Context context, Supplier telephonySupplier, int subId, int pid, int uid, + String callingPackage, @Nullable String callingFeatureId, String message) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); + + // SKIP checking for run-time permission since caller has PRIVILEGED permission + return true; + } catch (SecurityException privilegedPhoneStateException) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); + } catch (SecurityException phoneStateException) { + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for reading phone state. + if (SubscriptionManager.isValidSubscriptionId(subId)) { + enforceCarrierPrivilege(telephonySupplier, subId, uid, message); + return true; + } + throw phoneStateException; + } + } + + // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been + // revoked. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; + } + + /** + * Check whether the app with the given pid/uid can read phone state, or has carrier + * privileges on any active subscription. + * + *

If the app does not have carrier privilege, this method will return {@code false} instead + * of throwing a SecurityException. Therefore, the callers cannot tell the difference + * between M+ apps which declare the runtime permission but do not have it, and pre-M apps + * which declare the static permission but had access revoked via AppOps. Apps in the former + * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for + * use only if the behavior in both scenarios is meant to be identical. + * + * @return {@code true} if the app can read phone state or has carrier privilege; + * {@code false} otherwise. + */ + public static boolean checkReadPhoneStateOnAnyActiveSub(Context context, int pid, int uid, + String callingPackage, @Nullable String callingFeatureId, String message) { + return checkReadPhoneStateOnAnyActiveSub(context, TELEPHONY_SUPPLIER, pid, uid, + callingPackage, callingFeatureId, message); + } + + /** + * Check whether the app with the given pid/uid can read phone state, or has carrier + * privileges on any active subscription. + * + *

If the app does not have carrier privilege, this method will return {@code false} instead + * of throwing a SecurityException. Therefore, the callers cannot tell the difference + * between M+ apps which declare the runtime permission but do not have it, and pre-M apps + * which declare the static permission but had access revoked via AppOps. Apps in the former + * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for + * use only if the behavior in both scenarios is meant to be identical. + * + * @return {@code true} if the app can read phone state or has carrier privilege; + * {@code false} otherwise. + */ + @VisibleForTesting + public static boolean checkReadPhoneStateOnAnyActiveSub( + Context context, Supplier telephonySupplier, int pid, int uid, + String callingPackage, @Nullable String callingFeatureId, String message) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); + + // SKIP checking for run-time permission since caller has PRIVILEGED permission + return true; + } catch (SecurityException privilegedPhoneStateException) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); + } catch (SecurityException phoneStateException) { + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for reading phone state. + return checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid); + } + } + + // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been + // revoked. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; + } + + /** + * Check whether the caller (or self, if not processing an IPC) can read device identifiers. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check, or the calling package has carrier privileges on any active subscription. + *
  • throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission + * or carrier privileges of any active subscription. + *
  • return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission. In this case the caller would expect to have access to the device + * identifiers so false is returned instead of throwing a SecurityException to indicate + * the calling function should return dummy data. + *
+ */ + public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, + String callingPackage, @Nullable String callingFeatureId, String message) { + return checkCallingOrSelfReadDeviceIdentifiers(context, + SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, callingFeatureId, + message); + } + + /** + * Check whether the caller (or self, if not processing an IPC) can read device identifiers. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check, or the calling package has carrier privileges on any active subscription. + *
  • throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission + * or carrier privileges of any active subscription. + *
  • return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission or carrier privileges. In this case the caller would expect to have access + * to the device identifiers so false is returned instead of throwing a SecurityException + * to indicate the calling function should return dummy data. + *
+ */ + public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, int subId, + String callingPackage, @Nullable String callingFeatureId, String message) { + return checkPrivilegedReadPermissionOrCarrierPrivilegePermission( + context, subId, callingPackage, callingFeatureId, message, true); + } + + /** + * Check whether the caller (or self, if not processing an IPC) can read subscriber identifiers. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check, or the calling package has carrier privileges on specified subscription. + *
  • throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission. + *
  • return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission. In this case the caller would expect to have access to the device + * identifiers so false is returned instead of throwing a SecurityException to indicate + * the calling function should return dummy data. + *
+ */ + public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId, + String callingPackage, @Nullable String callingFeatureId, String message) { + return checkPrivilegedReadPermissionOrCarrierPrivilegePermission( + context, subId, callingPackage, callingFeatureId, message, false); + } + + /** + * Checks whether the app with the given pid/uid can read device identifiers. + * + *

This method behaves in one of the following ways: + *

    + *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check; or the calling package has carrier privileges on the specified + * subscription; or allowCarrierPrivilegeOnAnySub is true and has carrier privilege on + * any active subscription. + *
  • throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission. + *
  • return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission. In this case the caller would expect to have access to the device + * identifiers so false is returned instead of throwing a SecurityException to indicate + * the calling function should return dummy data. + *
+ */ + private static boolean checkPrivilegedReadPermissionOrCarrierPrivilegePermission( + Context context, int subId, String callingPackage, @Nullable String callingFeatureId, + String message, boolean allowCarrierPrivilegeOnAnySub) { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + // Allow system and root access to the device identifiers. + final int appId = UserHandle.getAppId(uid); + if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) { + return true; + } + // Allow access to packages that have the READ_PRIVILEGED_PHONE_STATE permission. + if (context.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, + uid) == PackageManager.PERMISSION_GRANTED) { + return true; + } + + // If the calling package has carrier privileges for specified sub, then allow access. + if (checkCarrierPrivilegeForSubId(subId)) return true; + + // If the calling package has carrier privileges for any subscription + // and allowCarrierPrivilegeOnAnySub is set true, then allow access. + if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId( + context, TELEPHONY_SUPPLIER, uid)) { + return true; + } + + // if the calling package is not null then perform the DevicePolicyManager device / + // profile owner and Appop checks. + if (callingPackage != null) { + // Allow access to an app that has been granted the READ_DEVICE_IDENTIFIERS app op. + long token = Binder.clearCallingIdentity(); + AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService( + Context.APP_OPS_SERVICE); + try { + if (appOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, uid, + callingPackage, callingFeatureId, null) == AppOpsManager.MODE_ALLOWED) { + return true; + } + } finally { + Binder.restoreCallingIdentity(token); + } + // Allow access to a device / profile owner app. + DevicePolicyManager devicePolicyManager = + (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccess( + callingPackage, pid, uid)) { + return true; + } + } + return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage, + message); + } + + /** + * Reports a failure when the app with the given pid/uid cannot access the requested identifier. + * + * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission or carrier privileges. + * @throws SecurityException if the caller does not meet any of the requirements for the + * requested identifier and is targeting Q or is targeting pre-Q + * and does not have the READ_PHONE_STATE permission or carrier + * privileges. + */ + private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid, + int uid, String callingPackage, String message) { + boolean isPreinstalled = false; + ApplicationInfo callingPackageInfo = null; + try { + callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser( + callingPackage, 0, UserHandle.getUserHandleForUid(uid)); + if (callingPackageInfo != null) { + if (callingPackageInfo.isSystemApp()) { + isPreinstalled = true; + } + } + } catch (PackageManager.NameNotFoundException e) { + // If the application info for the calling package could not be found then assume the + // calling app is a non-preinstalled app to detect any issues with the check + Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage, + e); + } + // The current package should only be reported in StatsLog if it has not previously been + // reported for the currently invoked device identifier method. + boolean packageReported = sReportedDeviceIDPackages.containsKey(callingPackage); + if (!packageReported || !sReportedDeviceIDPackages.get(callingPackage).contains( + message)) { + Set invokedMethods; + if (!packageReported) { + invokedMethods = new HashSet(); + sReportedDeviceIDPackages.put(callingPackage, invokedMethods); + } else { + invokedMethods = sReportedDeviceIDPackages.get(callingPackage); + } + invokedMethods.add(message); + StatsLog.write(StatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, callingPackage, message, + isPreinstalled, false); + } + Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + + ":isPreinstalled=" + isPreinstalled); + // if the target SDK is pre-Q then check if the calling package would have previously + // had access to device identifiers. + if (callingPackageInfo != null && ( + callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) { + if (context.checkPermission( + android.Manifest.permission.READ_PHONE_STATE, + pid, + uid) == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (checkCarrierPrivilegeForSubId(subId)) { + return false; + } + } + throw new SecurityException(message + ": The user " + uid + + " does not meet the requirements to access device identifiers."); + } + + /** + * Check whether the app with the given pid/uid can read the call log. + * @return {@code true} if the specified app has the read call log permission and AppOpp granted + * to it, {@code false} otherwise. + */ + public static boolean checkReadCallLog( + Context context, int subId, int pid, int uid, String callingPackage, + @Nullable String callingPackageName) { + return checkReadCallLog( + context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingPackageName); + } + + /** + * Check whether the app with the given pid/uid can read the call log. + * @return {@code true} if the specified app has the read call log permission and AppOpp granted + * to it, {@code false} otherwise. + */ + @VisibleForTesting + public static boolean checkReadCallLog( + Context context, Supplier telephonySupplier, int subId, int pid, int uid, + String callingPackage, @Nullable String callingFeatureId) { + + if (context.checkPermission(Manifest.permission.READ_CALL_LOG, pid, uid) + != PERMISSION_GRANTED) { + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for being able to see the call phone numbers. + if (SubscriptionManager.isValidSubscriptionId(subId)) { + enforceCarrierPrivilege(telephonySupplier, subId, uid, "readCallLog"); + return true; + } + return false; + } + + // We have READ_CALL_LOG permission, so return true as long as the AppOps bit hasn't been + // revoked. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + return appOps.noteOp(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; + } + + /** + * Returns whether the caller can read phone numbers. + * + *

Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the + * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers. + */ + public static boolean checkCallingOrSelfReadPhoneNumber( + Context context, int subId, String callingPackage, @Nullable String callingFeatureId, + String message) { + return checkReadPhoneNumber( + context, TELEPHONY_SUPPLIER, subId, Binder.getCallingPid(), Binder.getCallingUid(), + callingPackage, callingFeatureId, message); + } + + /** + * Returns whether the caller can read phone numbers. + * + *

Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the + * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers. + */ + @VisibleForTesting + public static boolean checkReadPhoneNumber( + Context context, Supplier telephonySupplier, int subId, int pid, int uid, + String callingPackage, @Nullable String callingFeatureId, String message) { + // Default SMS app can always read it. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + if (appOps.noteOp(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage, callingFeatureId, + null) == AppOpsManager.MODE_ALLOWED) { + return true; + } + + // NOTE(b/73308711): If an app has one of the following AppOps bits explicitly revoked, they + // will be denied access, even if they have another permission and AppOps bit if needed. + + // First, check if we can read the phone state. + try { + return checkReadPhoneState( + context, telephonySupplier, subId, pid, uid, callingPackage, callingFeatureId, + message); + } catch (SecurityException readPhoneStateSecurityException) { + } + // Can be read with READ_SMS too. + try { + context.enforcePermission(android.Manifest.permission.READ_SMS, pid, uid, message); + return appOps.noteOp(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; + + } catch (SecurityException readSmsSecurityException) { + } + // Can be read with READ_PHONE_NUMBERS too. + try { + context.enforcePermission(android.Manifest.permission.READ_PHONE_NUMBERS, pid, uid, + message); + return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; + + } catch (SecurityException readPhoneNumberSecurityException) { + } + + throw new SecurityException(message + ": Neither user " + uid + + " nor current process has " + android.Manifest.permission.READ_PHONE_STATE + + ", " + android.Manifest.permission.READ_SMS + ", or " + + android.Manifest.permission.READ_PHONE_NUMBERS); + } + + /** + * Ensure the caller (or self, if not processing an IPC) has MODIFY_PHONE_STATE (and is thus a + * privileged app) or carrier privileges. + * + * @throws SecurityException if the caller does not have the required permission/privileges + */ + public static void enforceCallingOrSelfModifyPermissionOrCarrierPrivilege( + Context context, int subId, String message) { + if (context.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + == PERMISSION_GRANTED) { + return; + } + + if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next."); + enforceCallingOrSelfCarrierPrivilege(subId, message); + } + + /** + * Ensure the caller (or self, if not processing an IPC) has + * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges. + * + * @throws SecurityException if the caller does not have the required permission/privileges + */ + public static void enforeceCallingOrSelfReadPhoneStatePermissionOrCarrierPrivilege( + Context context, int subId, String message) { + if (context.checkCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE) + == PERMISSION_GRANTED) { + return; + } + + if (DBG) { + Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); + } + + enforceCallingOrSelfCarrierPrivilege(subId, message); + } + + /** + * Ensure the caller (or self, if not processing an IPC) has + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or carrier privileges. + * + * @throws SecurityException if the caller does not have the required permission/privileges + */ + public static void enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege( + Context context, int subId, String message) { + if (context.checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + == PERMISSION_GRANTED) { + return; + } + + if (DBG) { + Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + + "check carrier privilege next."); + } + + enforceCallingOrSelfCarrierPrivilege(subId, message); + } + + /** + * Make sure the caller (or self, if not processing an IPC) has carrier privileges. + * + * @throws SecurityException if the caller does not have the required privileges + */ + public static void enforceCallingOrSelfCarrierPrivilege(int subId, String message) { + // NOTE: It's critical that we explicitly pass the calling UID here rather than call + // TelephonyManager#hasCarrierPrivileges directly, as the latter only works when called from + // the phone process. When called from another process, it will check whether that process + // has carrier privileges instead. + enforceCarrierPrivilege(subId, Binder.getCallingUid(), message); + } + + private static void enforceCarrierPrivilege(int subId, int uid, String message) { + enforceCarrierPrivilege(TELEPHONY_SUPPLIER, subId, uid, message); + } + + private static void enforceCarrierPrivilege( + Supplier telephonySupplier, int subId, int uid, String message) { + if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid) + != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); + throw new SecurityException(message); + } + } + + /** Returns whether the provided uid has carrier privileges for any active subscription ID. */ + private static boolean checkCarrierPrivilegeForAnySubId( + Context context, Supplier telephonySupplier, int uid) { + SubscriptionManager sm = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false); + for (int activeSubId : activeSubIds) { + if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + return false; + } + + private static int getCarrierPrivilegeStatus( + Supplier telephonySupplier, int subId, int uid) { + ITelephony telephony = telephonySupplier.get(); + try { + if (telephony != null) { + return telephony.getCarrierPrivilegeStatusForUid(subId, uid); + } + } catch (RemoteException e) { + // Fallback below. + } + Rlog.e(LOG_TAG, "Phone process is down, cannot check carrier privileges"); + return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; + } + + /** + * Throws if the caller is not of a shell (or root) UID. + * + * @param callingUid pass Binder.callingUid(). + */ + public static void enforceShellOnly(int callingUid, String message) { + if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + return; // okay + } + + throw new SecurityException(message + ": Only shell user can call it"); + } +} -- cgit v1.2.3 From 145294168f3bc9155a0641c7ef4ef5b7ff8c30a6 Mon Sep 17 00:00:00 2001 From: Shuo Qian Date: Wed, 4 Dec 2019 20:23:35 -0800 Subject: Cleanup ITelephony in TelephonyPermissions with new System API. This method is used by Telephony Module for permission checking. Test: manual; cts; unit tests in other CLs Bug: 145688189 Change-Id: Ie706e05e6a69e570331c1fe08a8df9677e0d9c8a --- .../internal/telephony/TelephonyPermissions.java | 153 +++++---------------- 1 file changed, 34 insertions(+), 119 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 86630b0eb991..4d01e330953a 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -27,8 +27,6 @@ import android.content.pm.PackageManager; import android.os.Binder; import android.os.Build; import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.telephony.Rlog; import android.telephony.SubscriptionManager; @@ -42,7 +40,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Supplier; /** Utility class for Telephony permission enforcement. */ public final class TelephonyPermissions { @@ -50,9 +47,6 @@ public final class TelephonyPermissions { private static final boolean DBG = false; - private static final Supplier TELEPHONY_SUPPLIER = () -> - ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); - /** * Whether to disable the new device identifier access restrictions. */ @@ -138,49 +132,6 @@ public final class TelephonyPermissions { public static boolean checkReadPhoneState( Context context, int subId, int pid, int uid, String callingPackage, @Nullable String callingFeatureId, String message) { - return checkReadPhoneState( - context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingFeatureId, - message); - } - - /** - * Check whether the calling packages has carrier privileges for the passing subscription. - * @return {@code true} if the caller has carrier privileges, {@false} otherwise. - */ - public static boolean checkCarrierPrivilegeForSubId(int subId) { - if (SubscriptionManager.isValidSubscriptionId(subId) - && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, Binder.getCallingUid()) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - return true; - } - return false; - } - - /** - * Check whether the app with the given pid/uid can read phone state. - * - *

This method behaves in one of the following ways: - *

    - *
  • return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the - * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. - *
  • throw SecurityException: if the caller didn't declare any of these permissions, or, for - * apps which support runtime permissions, if the caller does not currently have any of - * these permissions. - *
  • return false: if the caller lacks all of these permissions and doesn't support runtime - * permissions. This implies that the user revoked the ability to read phone state - * manually (via AppOps). In this case we can't throw as it would break app compatibility, - * so we return false to indicate that the calling function should return dummy data. - *
- * - *

Note: for simplicity, this method always returns false for callers using legacy - * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. - * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ - * devices. - */ - @VisibleForTesting - public static boolean checkReadPhoneState( - Context context, Supplier telephonySupplier, int subId, int pid, int uid, - String callingPackage, @Nullable String callingFeatureId, String message) { try { context.enforcePermission( android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); @@ -195,7 +146,7 @@ public final class TelephonyPermissions { // If we don't have the runtime permission, but do have carrier privileges, that // suffices for reading phone state. if (SubscriptionManager.isValidSubscriptionId(subId)) { - enforceCarrierPrivilege(telephonySupplier, subId, uid, message); + enforceCarrierPrivilege(context, subId, uid, message); return true; } throw phoneStateException; @@ -210,23 +161,16 @@ public final class TelephonyPermissions { } /** - * Check whether the app with the given pid/uid can read phone state, or has carrier - * privileges on any active subscription. - * - *

If the app does not have carrier privilege, this method will return {@code false} instead - * of throwing a SecurityException. Therefore, the callers cannot tell the difference - * between M+ apps which declare the runtime permission but do not have it, and pre-M apps - * which declare the static permission but had access revoked via AppOps. Apps in the former - * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for - * use only if the behavior in both scenarios is meant to be identical. - * - * @return {@code true} if the app can read phone state or has carrier privilege; - * {@code false} otherwise. + * Check whether the calling packages has carrier privileges for the passing subscription. + * @return {@code true} if the caller has carrier privileges, {@false} otherwise. */ - public static boolean checkReadPhoneStateOnAnyActiveSub(Context context, int pid, int uid, - String callingPackage, @Nullable String callingFeatureId, String message) { - return checkReadPhoneStateOnAnyActiveSub(context, TELEPHONY_SUPPLIER, pid, uid, - callingPackage, callingFeatureId, message); + public static boolean checkCarrierPrivilegeForSubId(Context context, int subId) { + if (SubscriptionManager.isValidSubscriptionId(subId) + && getCarrierPrivilegeStatus(context, subId, Binder.getCallingUid()) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + return false; } /** @@ -243,9 +187,7 @@ public final class TelephonyPermissions { * @return {@code true} if the app can read phone state or has carrier privilege; * {@code false} otherwise. */ - @VisibleForTesting - public static boolean checkReadPhoneStateOnAnyActiveSub( - Context context, Supplier telephonySupplier, int pid, int uid, + public static boolean checkReadPhoneStateOnAnyActiveSub(Context context, int pid, int uid, String callingPackage, @Nullable String callingFeatureId, String message) { try { context.enforcePermission( @@ -260,7 +202,7 @@ public final class TelephonyPermissions { } catch (SecurityException phoneStateException) { // If we don't have the runtime permission, but do have carrier privileges, that // suffices for reading phone state. - return checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid); + return checkCarrierPrivilegeForAnySubId(context, uid); } } @@ -375,12 +317,11 @@ public final class TelephonyPermissions { } // If the calling package has carrier privileges for specified sub, then allow access. - if (checkCarrierPrivilegeForSubId(subId)) return true; + if (checkCarrierPrivilegeForSubId(context, subId)) return true; // If the calling package has carrier privileges for any subscription // and allowCarrierPrivilegeOnAnySub is set true, then allow access. - if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId( - context, TELEPHONY_SUPPLIER, uid)) { + if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId(context, uid)) { return true; } @@ -468,7 +409,7 @@ public final class TelephonyPermissions { uid) == PackageManager.PERMISSION_GRANTED) { return false; } - if (checkCarrierPrivilegeForSubId(subId)) { + if (checkCarrierPrivilegeForSubId(context, subId)) { return false; } } @@ -484,26 +425,12 @@ public final class TelephonyPermissions { public static boolean checkReadCallLog( Context context, int subId, int pid, int uid, String callingPackage, @Nullable String callingPackageName) { - return checkReadCallLog( - context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingPackageName); - } - - /** - * Check whether the app with the given pid/uid can read the call log. - * @return {@code true} if the specified app has the read call log permission and AppOpp granted - * to it, {@code false} otherwise. - */ - @VisibleForTesting - public static boolean checkReadCallLog( - Context context, Supplier telephonySupplier, int subId, int pid, int uid, - String callingPackage, @Nullable String callingFeatureId) { - if (context.checkPermission(Manifest.permission.READ_CALL_LOG, pid, uid) != PERMISSION_GRANTED) { // If we don't have the runtime permission, but do have carrier privileges, that // suffices for being able to see the call phone numbers. if (SubscriptionManager.isValidSubscriptionId(subId)) { - enforceCarrierPrivilege(telephonySupplier, subId, uid, "readCallLog"); + enforceCarrierPrivilege(context, subId, uid, "readCallLog"); return true; } return false; @@ -513,7 +440,7 @@ public final class TelephonyPermissions { // revoked. AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); return appOps.noteOp(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage, - callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; + callingPackageName, null) == AppOpsManager.MODE_ALLOWED; } /** @@ -526,7 +453,7 @@ public final class TelephonyPermissions { Context context, int subId, String callingPackage, @Nullable String callingFeatureId, String message) { return checkReadPhoneNumber( - context, TELEPHONY_SUPPLIER, subId, Binder.getCallingPid(), Binder.getCallingUid(), + context, subId, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId, message); } @@ -538,7 +465,7 @@ public final class TelephonyPermissions { */ @VisibleForTesting public static boolean checkReadPhoneNumber( - Context context, Supplier telephonySupplier, int subId, int pid, int uid, + Context context, int subId, int pid, int uid, String callingPackage, @Nullable String callingFeatureId, String message) { // Default SMS app can always read it. AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); @@ -553,7 +480,7 @@ public final class TelephonyPermissions { // First, check if we can read the phone state. try { return checkReadPhoneState( - context, telephonySupplier, subId, pid, uid, callingPackage, callingFeatureId, + context, subId, pid, uid, callingPackage, callingFeatureId, message); } catch (SecurityException readPhoneStateSecurityException) { } @@ -595,7 +522,7 @@ public final class TelephonyPermissions { } if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next."); - enforceCallingOrSelfCarrierPrivilege(subId, message); + enforceCallingOrSelfCarrierPrivilege(context, subId, message); } /** @@ -615,7 +542,7 @@ public final class TelephonyPermissions { Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); } - enforceCallingOrSelfCarrierPrivilege(subId, message); + enforceCallingOrSelfCarrierPrivilege(context, subId, message); } /** @@ -636,7 +563,7 @@ public final class TelephonyPermissions { + "check carrier privilege next."); } - enforceCallingOrSelfCarrierPrivilege(subId, message); + enforceCallingOrSelfCarrierPrivilege(context, subId, message); } /** @@ -644,21 +571,18 @@ public final class TelephonyPermissions { * * @throws SecurityException if the caller does not have the required privileges */ - public static void enforceCallingOrSelfCarrierPrivilege(int subId, String message) { + public static void enforceCallingOrSelfCarrierPrivilege( + Context context, int subId, String message) { // NOTE: It's critical that we explicitly pass the calling UID here rather than call // TelephonyManager#hasCarrierPrivileges directly, as the latter only works when called from // the phone process. When called from another process, it will check whether that process // has carrier privileges instead. - enforceCarrierPrivilege(subId, Binder.getCallingUid(), message); - } - - private static void enforceCarrierPrivilege(int subId, int uid, String message) { - enforceCarrierPrivilege(TELEPHONY_SUPPLIER, subId, uid, message); + enforceCarrierPrivilege(context, subId, Binder.getCallingUid(), message); } private static void enforceCarrierPrivilege( - Supplier telephonySupplier, int subId, int uid, String message) { - if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid) + Context context, int subId, int uid, String message) { + if (getCarrierPrivilegeStatus(context, subId, uid) != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); throw new SecurityException(message); @@ -666,13 +590,12 @@ public final class TelephonyPermissions { } /** Returns whether the provided uid has carrier privileges for any active subscription ID. */ - private static boolean checkCarrierPrivilegeForAnySubId( - Context context, Supplier telephonySupplier, int uid) { + private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) { SubscriptionManager sm = (SubscriptionManager) context.getSystemService( Context.TELEPHONY_SUBSCRIPTION_SERVICE); int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false); for (int activeSubId : activeSubIds) { - if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid) + if (getCarrierPrivilegeStatus(context, activeSubId, uid) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return true; } @@ -680,18 +603,10 @@ public final class TelephonyPermissions { return false; } - private static int getCarrierPrivilegeStatus( - Supplier telephonySupplier, int subId, int uid) { - ITelephony telephony = telephonySupplier.get(); - try { - if (telephony != null) { - return telephony.getCarrierPrivilegeStatusForUid(subId, uid); - } - } catch (RemoteException e) { - // Fallback below. - } - Rlog.e(LOG_TAG, "Phone process is down, cannot check carrier privileges"); - return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; + private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) { + TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + return telephonyManager.createForSubscriptionId(subId).getCarrierPrivilegeStatus(uid); } /** -- cgit v1.2.3 From 814bf4720f79b3150572d8966926c3ddad655000 Mon Sep 17 00:00:00 2001 From: Shuo Qian Date: Fri, 13 Dec 2019 15:02:40 -0800 Subject: Check calling identify for TelephonyPermission Test: Treehugger; Request App compat testing run Bug: 145688189 Bug: 146213832 Change-Id: If3adcd17916be2e81497061120d1f350bfb8026d --- .../com/android/internal/telephony/TelephonyPermissions.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 4d01e330953a..fbb1380efcc1 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -604,9 +604,14 @@ public final class TelephonyPermissions { } private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) { - TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( + final long identity = Binder.clearCallingIdentity(); + try { + TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE); - return telephonyManager.createForSubscriptionId(subId).getCarrierPrivilegeStatus(uid); + return telephonyManager.createForSubscriptionId(subId).getCarrierPrivilegeStatus(uid); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** -- cgit v1.2.3 From d4de2c34adcf804322d187d6c69bdd431e49795e Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Thu, 12 Dec 2019 19:44:43 -0800 Subject: Make DPM#checkDeviceIdentifierAccess a SystemApi The device identifier check is performed within Telephony; one of the checks uses a hidden method within DevicePolicyManager to perform a device / profile owner access check. In order for Telephony to invoke this method as a mainline module it must be made into a SystemApi. Fixes: 145163986 Test: atest DeviceOwnerTest#testDeviceOwnerCanGetDeviceIdentifiers Test: atest DeviceOwnerTest#testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission Test: atest ManagedProfileTest#testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testProfileOwnerCanGetDeviceIdentifiers Test: atest TelephonyPermissionsTest Change-Id: Ica015f848209fc693a44fa019b25796260191e64 --- .../common/com/android/internal/telephony/TelephonyPermissions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 86630b0eb991..bd22787d90cf 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -403,7 +403,7 @@ public final class TelephonyPermissions { DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccess( + if (devicePolicyManager != null && devicePolicyManager.hasDeviceIdentifierAccess( callingPackage, pid, uid)) { return true; } -- cgit v1.2.3 From d6552acf19b6139038b97fbf709b2099add03499 Mon Sep 17 00:00:00 2001 From: Daniel Bright Date: Mon, 6 Jan 2020 12:47:14 -0800 Subject: Change get sub id list to get sub info list Bug: 147239065 Test: FrameworkBaseTests Change-Id: Id9860eb48cffbe6c5517e51d1c6ed29a84997bbd --- telephony/common/com/google/android/mms/pdu/PduPersister.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java index b237705274da..8efca0ea3909 100755 --- a/telephony/common/com/google/android/mms/pdu/PduPersister.java +++ b/telephony/common/com/google/android/mms/pdu/PduPersister.java @@ -34,6 +34,7 @@ import android.provider.Telephony.MmsSms; import android.provider.Telephony.MmsSms.PendingMessages; import android.provider.Telephony.Threads; import android.telephony.PhoneNumberUtils; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -1448,9 +1449,9 @@ public class PduPersister { final Set myPhoneNumbers = new HashSet(); if (excludeMyNumber) { // Build a list of my phone numbers from the various sims. - for (int subid : subscriptionManager.getActiveSubscriptionIdList()) { + for (SubscriptionInfo subInfo : subscriptionManager.getActiveSubscriptionInfoList()) { final String myNumber = mContext.getSystemService(TelephonyManager.class). - createForSubscriptionId(subid).getLine1Number(); + createForSubscriptionId(subInfo.getSubscriptionId()).getLine1Number(); if (myNumber != null) { myPhoneNumbers.add(myNumber); } -- cgit v1.2.3 From 5edd9b25418d4c7e109ed3502733766e5472a1dc Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Tue, 31 Dec 2019 14:58:46 -0800 Subject: Fix hidden API usage in PackageChangeReceiver Use Context#createContextForUser instead of calling Context#registerReceiverForUser. Test: atest FrameworksTelephonyTests Bug: 146834818 Change-Id: Ia239499219218d30d1556007ac4561a5ec44b72f --- .../com/android/internal/telephony/PackageChangeReceiver.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java index 922af126e73e..d4d07ea8e54c 100644 --- a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java +++ b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java @@ -56,12 +56,8 @@ public abstract class PackageChangeReceiver extends BroadcastReceiver { Handler handler = (thread == null) ? BackgroundThread.getHandler() : new Handler(thread); mRegisteredContext = context; if (handler != null) { - if (user != null) { - context.registerReceiverAsUser(this, user, sPackageIntentFilter, null, handler); - } else { - context.registerReceiver(this, sPackageIntentFilter, - null, handler); - } + Context contextForUser = user == null ? context : context.createContextAsUser(user, 0); + contextForUser.registerReceiver(this, sPackageIntentFilter, null, handler); } else { throw new NullPointerException(); } -- cgit v1.2.3 From 070380947280e2dac930f9504ffc288a718c70b5 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Fri, 3 Jan 2020 16:38:19 -0800 Subject: Fix usages of PackageManager APIs in Telephony Remove usages of the hidden PackageManager#*asUser methods from Telephony, and add some unit tests to verify the functionality. Test: atest FrameworksTelephonyTests Test: atest TelephonyCommonTests Bug: 146834818 Change-Id: I2e2ec2c421773e3ff58556373896238e1ff0678a --- .../internal/telephony/PackageChangeReceiver.java | 30 ++++++---- .../android/internal/telephony/SmsApplication.java | 65 ++++++++++++---------- 2 files changed, 54 insertions(+), 41 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java index d4d07ea8e54c..0b47547d3b0c 100644 --- a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java +++ b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java @@ -24,17 +24,17 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.UserHandle; -import com.android.internal.os.BackgroundThread; - /** * Helper class for monitoring the state of packages: adding, removing, * updating, and disappearing and reappearing on the SD card. */ public abstract class PackageChangeReceiver extends BroadcastReceiver { static final IntentFilter sPackageIntentFilter = new IntentFilter(); + private static HandlerThread sHandlerThread; static { sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -43,24 +43,24 @@ public abstract class PackageChangeReceiver extends BroadcastReceiver { sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); sPackageIntentFilter.addDataScheme("package"); } + // Keep an instance of Context around as long as we still want the receiver: + // if the instance of Context gets garbage-collected, it'll unregister the receiver, so only + // unset when we want to unregister. Context mRegisteredContext; /** - * To register the intents that needed for monitoring the state of packages + * To register the intents that needed for monitoring the state of packages. Once this method + * has been called on an instance of {@link PackageChangeReceiver}, all subsequent calls must + * have the same {@code user} argument. */ public void register(@NonNull Context context, @Nullable Looper thread, @Nullable UserHandle user) { if (mRegisteredContext != null) { throw new IllegalStateException("Already registered"); } - Handler handler = (thread == null) ? BackgroundThread.getHandler() : new Handler(thread); - mRegisteredContext = context; - if (handler != null) { - Context contextForUser = user == null ? context : context.createContextAsUser(user, 0); - contextForUser.registerReceiver(this, sPackageIntentFilter, null, handler); - } else { - throw new NullPointerException(); - } + Handler handler = new Handler(thread == null ? getStaticLooper() : thread); + mRegisteredContext = user == null ? context : context.createContextAsUser(user, 0); + mRegisteredContext.registerReceiver(this, sPackageIntentFilter, null, handler); } /** @@ -74,6 +74,14 @@ public abstract class PackageChangeReceiver extends BroadcastReceiver { mRegisteredContext = null; } + private static synchronized Looper getStaticLooper() { + if (sHandlerThread == null) { + sHandlerThread = new HandlerThread(PackageChangeReceiver.class.getSimpleName()); + sHandlerThread.start(); + } + return sHandlerThread.getLooper(); + } + /** * This method is invoked when receive the Intent.ACTION_PACKAGE_ADDED */ diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 0275cb98eda9..b30258906368 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -58,6 +58,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Class for managing the primary application that we will deliver SMS/MMS messages to @@ -248,6 +249,7 @@ public final class SmsApplication { private static Collection getApplicationCollectionInternal( Context context, int userId) { PackageManager packageManager = context.getPackageManager(); + UserHandle userHandle = UserHandle.of(userId); // Get the list of apps registered for SMS Intent intent = new Intent(Intents.SMS_DELIVER_ACTION); @@ -256,7 +258,7 @@ public final class SmsApplication { } List smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - userId); + userHandle); HashMap receivers = new HashMap(); @@ -283,7 +285,7 @@ public final class SmsApplication { intent.setDataAndType(null, "application/vnd.wap.mms-message"); List mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - userId); + userHandle); for (ResolveInfo resolveInfo : mmsReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { @@ -325,7 +327,7 @@ public final class SmsApplication { Uri.fromParts(SCHEME_SMSTO, "", null)); List sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - userId); + userHandle); for (ResolveInfo resolveInfo : sendToActivities) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { @@ -343,7 +345,7 @@ public final class SmsApplication { List smsAppChangedReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" + smsAppChangedReceivers); @@ -370,7 +372,7 @@ public final class SmsApplication { List providerChangedReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" + providerChangedReceivers); @@ -397,7 +399,7 @@ public final class SmsApplication { List simFullReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers=" + simFullReceivers); @@ -626,7 +628,8 @@ public final class SmsApplication { } // We only make the change if the new package is valid - PackageManager packageManager = context.getPackageManager(); + PackageManager packageManager = + context.createContextAsUser(userHandle, 0).getPackageManager(); Collection applications = getApplicationCollectionInternal( context, userId); SmsApplicationData oldAppData = oldPackageName != null ? @@ -637,8 +640,7 @@ public final class SmsApplication { AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); if (oldPackageName != null) { try { - int uid = packageManager.getPackageInfoAsUser( - oldPackageName, 0, userId).applicationInfo.uid; + int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid; setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT); } catch (NameNotFoundException e) { Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); @@ -802,9 +804,16 @@ public final class SmsApplication { } private void onPackageChanged() { - PackageManager packageManager = mContext.getPackageManager(); + int userId; + try { + userId = getSendingUser().getIdentifier(); + } catch (NullPointerException e) { + // This should never happen in prod -- unit tests will put the receiver into a + // unusual state where the pending result is null, which produces a NPE when calling + // getSendingUserId. Just pretend like it's the system user for testing. + userId = UserHandle.USER_SYSTEM; + } Context userContext = mContext; - final int userId = getSendingUserId(); if (userId != UserHandle.USER_SYSTEM) { try { userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, @@ -815,10 +824,11 @@ public final class SmsApplication { } } } + PackageManager packageManager = userContext.getPackageManager(); // Ensure this component is still configured as the preferred activity ComponentName componentName = getDefaultSendToApplication(userContext, true); if (componentName != null) { - configurePreferredActivity(packageManager, componentName, userId); + configurePreferredActivity(packageManager, componentName); } } } @@ -830,41 +840,36 @@ public final class SmsApplication { @UnsupportedAppUsage private static void configurePreferredActivity(PackageManager packageManager, - ComponentName componentName, int userId) { + ComponentName componentName) { // Add the four activity preferences we want to direct to this app. - replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS); - replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO); - replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS); - replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO); + replacePreferredActivity(packageManager, componentName, SCHEME_SMS); + replacePreferredActivity(packageManager, componentName, SCHEME_SMSTO); + replacePreferredActivity(packageManager, componentName, SCHEME_MMS); + replacePreferredActivity(packageManager, componentName, SCHEME_MMSTO); } /** * Updates the ACTION_SENDTO activity to the specified component for the specified scheme. */ private static void replacePreferredActivity(PackageManager packageManager, - ComponentName componentName, int userId, String scheme) { + ComponentName componentName, String scheme) { // Build the set of existing activities that handle this scheme Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null)); - List resolveInfoList = packageManager.queryIntentActivitiesAsUser( - intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER, - userId); + List resolveInfoList = packageManager.queryIntentActivities( + intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER); - // Build the set of ComponentNames for these activities - final int n = resolveInfoList.size(); - ComponentName[] set = new ComponentName[n]; - for (int i = 0; i < n; i++) { - ResolveInfo info = resolveInfoList.get(i); - set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); - } + List components = resolveInfoList.stream().map(info -> + new ComponentName(info.activityInfo.packageName, info.activityInfo.name)) + .collect(Collectors.toList()); // Update the preferred SENDTO activity for the specified scheme IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SENDTO); intentFilter.addCategory(Intent.CATEGORY_DEFAULT); intentFilter.addDataScheme(scheme); - packageManager.replacePreferredActivityAsUser(intentFilter, + packageManager.replacePreferredActivity(intentFilter, IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL, - set, componentName, userId); + components, componentName); } /** -- cgit v1.2.3 From c61b1edc97ccbae2af500c50bfe480833a3845af Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Mon, 6 Jan 2020 16:26:05 -0800 Subject: [Telephony Mainline] Move CarrierAppUtils and LocationAccessPolicy to telephony common Bug: 146904426 Test: Build Change-Id: I6f6d24fd0adab803899b25a47199839bcbb09eb0 --- .../android/telephony/LocationAccessPolicy.java | 371 +++++++++++++++++++ .../internal/telephony/CarrierAppUtils.java | 400 +++++++++++++++++++++ .../internal/telephony/util/ArrayUtils.java | 243 +++++++++++++ 3 files changed, 1014 insertions(+) create mode 100644 telephony/common/android/telephony/LocationAccessPolicy.java create mode 100644 telephony/common/com/android/internal/telephony/CarrierAppUtils.java create mode 100644 telephony/common/com/android/internal/telephony/util/ArrayUtils.java (limited to 'telephony/common') diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java new file mode 100644 index 000000000000..f39981fdf25d --- /dev/null +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2020 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 android.telephony; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; +import android.widget.Toast; + +import com.android.internal.telephony.util.TelephonyUtils; + +import java.util.List; + +/** + * Helper for performing location access checks. + * @hide + */ +public final class LocationAccessPolicy { + private static final String TAG = "LocationAccessPolicy"; + private static final boolean DBG = false; + public static final int MAX_SDK_FOR_ANY_ENFORCEMENT = Build.VERSION_CODES.CUR_DEVELOPMENT; + + public enum LocationPermissionResult { + ALLOWED, + /** + * Indicates that the denial is due to a transient device state + * (e.g. app-ops, location master switch) + */ + DENIED_SOFT, + /** + * Indicates that the denial is due to a misconfigured app (e.g. missing entry in manifest) + */ + DENIED_HARD, + } + + /** Data structure for location permission query */ + public static class LocationPermissionQuery { + public final String callingPackage; + public final String callingFeatureId; + public final int callingUid; + public final int callingPid; + public final int minSdkVersionForCoarse; + public final int minSdkVersionForFine; + public final boolean logAsInfo; + public final String method; + + private LocationPermissionQuery(String callingPackage, @Nullable String callingFeatureId, + int callingUid, int callingPid, int minSdkVersionForCoarse, + int minSdkVersionForFine, boolean logAsInfo, String method) { + this.callingPackage = callingPackage; + this.callingFeatureId = callingFeatureId; + this.callingUid = callingUid; + this.callingPid = callingPid; + this.minSdkVersionForCoarse = minSdkVersionForCoarse; + this.minSdkVersionForFine = minSdkVersionForFine; + this.logAsInfo = logAsInfo; + this.method = method; + } + + /** Builder for LocationPermissionQuery */ + public static class Builder { + private String mCallingPackage; + private String mCallingFeatureId; + private int mCallingUid; + private int mCallingPid; + private int mMinSdkVersionForCoarse = Integer.MAX_VALUE; + private int mMinSdkVersionForFine = Integer.MAX_VALUE; + private boolean mLogAsInfo = false; + private String mMethod; + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPackage(String callingPackage) { + mCallingPackage = callingPackage; + return this; + } + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingFeatureId(@Nullable String callingFeatureId) { + mCallingFeatureId = callingFeatureId; + return this; + } + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingUid(int callingUid) { + mCallingUid = callingUid; + return this; + } + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPid(int callingPid) { + mCallingPid = callingPid; + return this; + } + + /** + * Apps that target at least this sdk version will be checked for coarse location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForCoarse( + int minSdkVersionForCoarse) { + mMinSdkVersionForCoarse = minSdkVersionForCoarse; + return this; + } + + /** + * Apps that target at least this sdk version will be checked for fine location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForFine( + int minSdkVersionForFine) { + mMinSdkVersionForFine = minSdkVersionForFine; + return this; + } + + /** + * Optional, for logging purposes only. + */ + public Builder setMethod(String method) { + mMethod = method; + return this; + } + + /** + * If called with {@code true}, log messages will only be printed at the info level. + */ + public Builder setLogAsInfo(boolean logAsInfo) { + mLogAsInfo = logAsInfo; + return this; + } + + /** build LocationPermissionQuery */ + public LocationPermissionQuery build() { + return new LocationPermissionQuery(mCallingPackage, mCallingFeatureId, + mCallingUid, mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine, + mLogAsInfo, mMethod); + } + } + } + + private static void logError(Context context, LocationPermissionQuery query, String errorMsg) { + if (query.logAsInfo) { + Log.i(TAG, errorMsg); + return; + } + Log.e(TAG, errorMsg); + try { + if (TelephonyUtils.IS_DEBUGGABLE) { + Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); + } + } catch (Throwable t) { + // whatever, not important + } + } + + private static LocationPermissionResult appOpsModeToPermissionResult(int appOpsMode) { + switch (appOpsMode) { + case AppOpsManager.MODE_ALLOWED: + return LocationPermissionResult.ALLOWED; + case AppOpsManager.MODE_ERRORED: + return LocationPermissionResult.DENIED_HARD; + default: + return LocationPermissionResult.DENIED_SOFT; + } + } + + private static String getAppOpsString(String manifestPermission) { + switch (manifestPermission) { + case Manifest.permission.ACCESS_FINE_LOCATION: + return AppOpsManager.OPSTR_FINE_LOCATION; + case Manifest.permission.ACCESS_COARSE_LOCATION: + return AppOpsManager.OPSTR_COARSE_LOCATION; + default: + return null; + } + } + + private static LocationPermissionResult checkAppLocationPermissionHelper(Context context, + LocationPermissionQuery query, String permissionToCheck) { + String locationTypeForLog = + Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? "fine" : "coarse"; + + // Do the app-ops and the manifest check without any of the allow-overrides first. + boolean hasManifestPermission = checkManifestPermission(context, query.callingPid, + query.callingUid, permissionToCheck); + + if (hasManifestPermission) { + // Only check the app op if the app has the permission. + int appOpMode = context.getSystemService(AppOpsManager.class) + .noteOpNoThrow(getAppOpsString(permissionToCheck), query.callingUid, + query.callingPackage, query.callingFeatureId, null); + if (appOpMode == AppOpsManager.MODE_ALLOWED) { + // If the app did everything right, return without logging. + return LocationPermissionResult.ALLOWED; + } else { + // If the app has the manifest permission but not the app-op permission, it means + // that it's aware of the requirement and the user denied permission explicitly. + // If we see this, don't let any of the overrides happen. + Log.i(TAG, query.callingPackage + " is aware of " + locationTypeForLog + " but the" + + " app-ops permission is specifically denied."); + return appOpsModeToPermissionResult(appOpMode); + } + } + + int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; + + // If the app fails for some reason, see if it should be allowed to proceed. + if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because we're not enforcing API " + minSdkVersion + " yet." + + " Please fix this app because it will break in the future. Called from " + + query.method; + logError(context, query, errorMsg); + return null; + } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because it doesn't target API " + minSdkVersion + " yet." + + " Please fix this app. Called from " + query.method; + logError(context, query, errorMsg); + return null; + } else { + // If we're not allowing it due to the above two conditions, this means that the app + // did not declare the permission in their manifest. + return LocationPermissionResult.DENIED_HARD; + } + } + + /** Check if location permissions have been granted */ + public static LocationPermissionResult checkLocationPermission( + Context context, LocationPermissionQuery query) { + // Always allow the phone process and system server to access location. This avoid + // breaking legacy code that rely on public-facing APIs to access cell location, and + // it doesn't create an info leak risk because the cell location is stored in the phone + // process anyway, and the system server already has location access. + if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID + || query.callingUid == Process.ROOT_UID) { + return LocationPermissionResult.ALLOWED; + } + + // Check the system-wide requirements. If the location master switch is off or + // the app's profile isn't in foreground, return a soft denial. + if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) { + return LocationPermissionResult.DENIED_SOFT; + } + + // Do the check for fine, then for coarse. + if (query.minSdkVersionForFine < Integer.MAX_VALUE) { + LocationPermissionResult resultForFine = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_FINE_LOCATION); + if (resultForFine != null) { + return resultForFine; + } + } + + if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) { + LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_COARSE_LOCATION); + if (resultForCoarse != null) { + return resultForCoarse; + } + } + + // At this point, we're out of location checks to do. If the app bypassed all the previous + // ones due to the SDK grandfathering schemes, allow it access. + return LocationPermissionResult.ALLOWED; + } + + + private static boolean checkManifestPermission(Context context, int pid, int uid, + String permissionToCheck) { + return context.checkPermission(permissionToCheck, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + + private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { + if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { + if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); + return false; + } + // If the user or profile is current, permission is granted. + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + } + + private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { + LocationManager locationManager = context.getSystemService(LocationManager.class); + if (locationManager == null) { + Log.w(TAG, "Couldn't get location manager, denying location access"); + return false; + } + return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); + } + + private static boolean checkInteractAcrossUsersFull( + @NonNull Context context, int pid, int uid) { + return checkManifestPermission(context, pid, uid, + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + + private static boolean isCurrentProfile(@NonNull Context context, int uid) { + long token = Binder.clearCallingIdentity(); + try { + final int currentUser = ActivityManager.getCurrentUser(); + final int callingUserId = UserHandle.getUserId(uid); + if (callingUserId == currentUser) { + return true; + } else { + List userProfiles = context.getSystemService( + UserManager.class).getProfiles(currentUser); + for (UserInfo user : userProfiles) { + if (user.id == callingUserId) { + return true; + } + } + } + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { + try { + if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion + >= sdkVersion) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume known app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking app's version. + } + return false; + } +} diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java new file mode 100644 index 000000000000..eb02ea6f5e40 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2020 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.internal.telephony; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.RemoteException; +import android.permission.IPermissionManager; +import android.provider.Settings; +import android.telephony.Rlog; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.util.ArrayUtils; +import com.android.server.SystemConfig; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Utilities for handling carrier applications. + * @hide + */ +public final class CarrierAppUtils { + private static final String TAG = "CarrierAppUtils"; + + private static final boolean DEBUG = false; // STOPSHIP if true + + private CarrierAppUtils() {} + + /** + * Handle preinstalled carrier apps which should be disabled until a matching SIM is inserted. + * + * Evaluates the list of applications in + * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierApps()}. We want to disable each + * such application which is present on the system image until the user inserts a SIM which + * causes that application to gain carrier privilege (indicating a "match"), without interfering + * with the user if they opt to enable/disable the app explicitly. + * + * So, for each such app, we either disable until used IFF the app is not carrier privileged AND + * in the default state (e.g. not explicitly DISABLED/DISABLED_BY_USER/ENABLED), or we enable if + * the app is carrier privileged and in either the default state or DISABLED_UNTIL_USED. + * + * In addition, there is a list of carrier-associated applications in + * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app in this + * list is associated with a carrier app. When the given carrier app is enabled/disabled per the + * above, the associated applications are enabled/disabled to match. + * + * When enabling a carrier app we also grant it default permissions. + * + * This method is idempotent and is safe to be called at any time; it should be called once at + * system startup prior to any application running, as well as any time the set of carrier + * privileged apps may have changed. + */ + public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, + IPackageManager packageManager, IPermissionManager permissionManager, + TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) { + if (DEBUG) { + Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + } + SystemConfig config = SystemConfig.getInstance(); + ArraySet systemCarrierAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierApps(); + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, + telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, + systemCarrierAssociatedAppsDisabledUntilUsed); + } + + /** + * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager, + * ContentResolver, int)}, but assumes that no carrier apps have carrier privileges. + * + * This prevents a potential race condition on first boot - since the app's default state is + * enabled, we will initially disable it when the telephony stack is first initialized as it has + * not yet read the carrier privilege rules. However, since telephony is initialized later on + * late in boot, the app being disabled may have already been started in response to certain + * broadcasts. The app will continue to run (briefly) after being disabled, before the Package + * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. + */ + public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, + IPackageManager packageManager, IPermissionManager permissionManager, + ContentResolver contentResolver, int userId) { + if (DEBUG) { + Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + } + SystemConfig config = SystemConfig.getInstance(); + ArraySet systemCarrierAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierApps(); + + + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, + null /* telephonyManager */, contentResolver, userId, + systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); + } + + /** + * Disable carrier apps until they are privileged + * Must be public b/c framework unit tests can't access package-private methods. + */ + @VisibleForTesting + public static void disableCarrierAppsUntilPrivileged(String callingPackage, + IPackageManager packageManager, IPermissionManager permissionManager, + @Nullable TelephonyManager telephonyManager, + ContentResolver contentResolver, int userId, + ArraySet systemCarrierAppsDisabledUntilUsed, + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { + List candidates = getDefaultCarrierAppCandidatesHelper(packageManager, + userId, systemCarrierAppsDisabledUntilUsed); + if (candidates == null || candidates.isEmpty()) { + return; + } + + Map> associatedApps = getDefaultCarrierAssociatedAppsHelper( + packageManager, + userId, + systemCarrierAssociatedAppsDisabledUntilUsed); + + List enabledCarrierPackages = new ArrayList<>(); + + boolean hasRunOnce = Settings.Secure.getIntForUser( + contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1; + + try { + for (ApplicationInfo ai : candidates) { + String packageName = ai.packageName; + String[] restrictedCarrierApps = Resources.getSystem().getStringArray( + R.array.config_restrictedPreinstalledCarrierApps); + boolean hasPrivileges = telephonyManager != null + && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS + && !ArrayUtils.contains(restrictedCarrierApps, packageName); + + // add hiddenUntilInstalled flag for carrier apps and associated apps + packageManager.setSystemAppHiddenUntilInstalled(packageName, true); + List associatedAppList = associatedApps.get(packageName); + if (associatedAppList != null) { + for (ApplicationInfo associatedApp : associatedAppList) { + packageManager.setSystemAppHiddenUntilInstalled( + associatedApp.packageName, + true + ); + } + } + + if (hasPrivileges) { + // Only update enabled state for the app on /system. Once it has been + // updated we shouldn't touch it. + if (!ai.isUpdatedSystemApp() + && (ai.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + || ai.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED + || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { + Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user " + + userId); + packageManager.setSystemAppInstallState( + packageName, + true /*installed*/, + userId); + packageManager.setApplicationEnabledSetting( + packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP, + userId, + callingPackage); + } + + // Also enable any associated apps for this carrier app. + if (associatedAppList != null) { + for (ApplicationInfo associatedApp : associatedAppList) { + if (associatedApp.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + || associatedApp.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED + || (associatedApp.flags + & ApplicationInfo.FLAG_INSTALLED) == 0) { + Rlog.i(TAG, "Update associated state(" + associatedApp.packageName + + "): ENABLED for user " + userId); + packageManager.setSystemAppInstallState( + associatedApp.packageName, + true /*installed*/, + userId); + packageManager.setApplicationEnabledSetting( + associatedApp.packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP, + userId, + callingPackage); + } + } + } + + // Always re-grant default permissions to carrier apps w/ privileges. + enabledCarrierPackages.add(ai.packageName); + } else { // No carrier privileges + // Only update enabled state for the app on /system. Once it has been + // updated we shouldn't touch it. + if (!ai.isUpdatedSystemApp() + && ai.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { + Rlog.i(TAG, "Update state(" + packageName + + "): DISABLED_UNTIL_USED for user " + userId); + packageManager.setSystemAppInstallState( + packageName, + false /*installed*/, + userId); + } + + // Also disable any associated apps for this carrier app if this is the first + // run. We avoid doing this a second time because it is brittle to rely on the + // distinction between "default" and "enabled". + if (!hasRunOnce) { + if (associatedAppList != null) { + for (ApplicationInfo associatedApp : associatedAppList) { + if (associatedApp.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + && (associatedApp.flags + & ApplicationInfo.FLAG_INSTALLED) != 0) { + Rlog.i(TAG, + "Update associated state(" + associatedApp.packageName + + "): DISABLED_UNTIL_USED for user " + userId); + packageManager.setSystemAppInstallState( + associatedApp.packageName, + false /*installed*/, + userId); + } + } + } + } + } + } + + // Mark the execution so we do not disable apps again. + if (!hasRunOnce) { + Settings.Secure.putIntForUser( + contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId); + } + + if (!enabledCarrierPackages.isEmpty()) { + // Since we enabled at least one app, ensure we grant default permissions to those + // apps. + String[] packageNames = new String[enabledCarrierPackages.size()]; + enabledCarrierPackages.toArray(packageNames); + permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); + } + } catch (RemoteException e) { + Rlog.w(TAG, "Could not reach PackageManager", e); + } + } + + /** + * Returns the list of "default" carrier apps. + * + * This is the subset of apps returned by + * {@link #getDefaultCarrierAppCandidates(IPackageManager, int)} which currently have carrier + * privileges per the SIM(s) inserted in the device. + */ + public static List getDefaultCarrierApps(IPackageManager packageManager, + TelephonyManager telephonyManager, int userId) { + // Get all system apps from the default list. + List candidates = getDefaultCarrierAppCandidates(packageManager, userId); + if (candidates == null || candidates.isEmpty()) { + return null; + } + + // Filter out apps without carrier privileges. + // Iterate from the end to avoid creating an Iterator object and because we will be removing + // elements from the list as we pass through it. + for (int i = candidates.size() - 1; i >= 0; i--) { + ApplicationInfo ai = candidates.get(i); + String packageName = ai.packageName; + boolean hasPrivileges = + telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + if (!hasPrivileges) { + candidates.remove(i); + } + } + + return candidates; + } + + /** + * Returns the list of "default" carrier app candidates. + * + * These are the apps subject to the hiding/showing logic in + * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, IPackageManager, + * TelephonyManager, ContentResolver, int)}, as well as the apps which should have default + * permissions granted, when a matching SIM is inserted. + * + * Whether or not the app is actually considered a default app depends on whether the app has + * carrier privileges as determined by the SIMs in the device. + */ + public static List getDefaultCarrierAppCandidates( + IPackageManager packageManager, int userId) { + ArraySet systemCarrierAppsDisabledUntilUsed = + SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps(); + return getDefaultCarrierAppCandidatesHelper(packageManager, userId, + systemCarrierAppsDisabledUntilUsed); + } + + private static List getDefaultCarrierAppCandidatesHelper( + IPackageManager packageManager, + int userId, + ArraySet systemCarrierAppsDisabledUntilUsed) { + if (systemCarrierAppsDisabledUntilUsed == null) { + return null; + } + + int size = systemCarrierAppsDisabledUntilUsed.size(); + if (size == 0) { + return null; + } + + List apps = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + ApplicationInfo ai = + getApplicationInfoIfSystemApp(packageManager, userId, packageName); + if (ai != null) { + apps.add(ai); + } + } + return apps; + } + + private static Map> getDefaultCarrierAssociatedAppsHelper( + IPackageManager packageManager, + int userId, + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { + int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); + Map> associatedApps = new ArrayMap<>(size); + for (int i = 0; i < size; i++) { + String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i); + List associatedAppPackages = + systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); + for (int j = 0; j < associatedAppPackages.size(); j++) { + ApplicationInfo ai = + getApplicationInfoIfSystemApp( + packageManager, userId, associatedAppPackages.get(j)); + // Only update enabled state for the app on /system. Once it has been updated we + // shouldn't touch it. + if (ai != null && !ai.isUpdatedSystemApp()) { + List appList = associatedApps.get(carrierAppPackage); + if (appList == null) { + appList = new ArrayList<>(); + associatedApps.put(carrierAppPackage, appList); + } + appList.add(ai); + } + } + } + return associatedApps; + } + + @Nullable + private static ApplicationInfo getApplicationInfoIfSystemApp( + IPackageManager packageManager, + int userId, + String packageName) { + try { + ApplicationInfo ai = packageManager.getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); + if (ai != null && ai.isSystemApp()) { + return ai; + } + } catch (RemoteException e) { + Rlog.w(TAG, "Could not reach PackageManager", e); + } + return null; + } +} diff --git a/telephony/common/com/android/internal/telephony/util/ArrayUtils.java b/telephony/common/com/android/internal/telephony/util/ArrayUtils.java new file mode 100644 index 000000000000..28401a6a7092 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/util/ArrayUtils.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2020 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.internal.telephony.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** Utility methods for array operations. */ +public final class ArrayUtils { + private ArrayUtils() { /* cannot be instantiated */ } + + /** + * Adds value to given array if not already present, providing set-like behavior. + * + * @param kind The class of the array elements. + * @param array The array to append to. + * @param element The array element to append. + * @return The array containing the appended element. + */ + @SuppressWarnings("unchecked") + @NonNull + public static T[] appendElement(Class kind, @Nullable T[] array, T element) { + return appendElement(kind, array, element, false); + } + + /** + * Adds value to given array. + * + * @param kind The class of the array elements. + * @param array The array to append to. + * @param element The array element to append. + * @param allowDuplicates Whether to allow duplicated elements in array. + * @return The array containing the appended element. + */ + @SuppressWarnings("unchecked") + @NonNull + public static T[] appendElement(Class kind, @Nullable T[] array, T element, + boolean allowDuplicates) { + final T[] result; + final int end; + if (array != null) { + if (!allowDuplicates && contains(array, element)) return array; + end = array.length; + result = (T[]) Array.newInstance(kind, end + 1); + System.arraycopy(array, 0, result, 0, end); + } else { + end = 0; + result = (T[]) Array.newInstance(kind, 1); + } + result[end] = element; + return result; + } + + /** + * Combine multiple arrays into a single array. + * + * @param kind The class of the array elements + * @param arrays The arrays to combine + * @param The class of the array elements (inferred from kind). + * @return A single array containing all the elements of the parameter arrays. + */ + @SuppressWarnings("unchecked") + @NonNull + public static T[] concatElements(Class kind, @Nullable T[]... arrays) { + if (arrays == null || arrays.length == 0) { + return createEmptyArray(kind); + } + + int totalLength = 0; + for (T[] item : arrays) { + if (item == null) { + continue; + } + + totalLength += item.length; + } + + // Optimization for entirely empty arrays. + if (totalLength == 0) { + return createEmptyArray(kind); + } + + final T[] all = (T[]) Array.newInstance(kind, totalLength); + int pos = 0; + for (T[] item : arrays) { + if (item == null || item.length == 0) { + continue; + } + System.arraycopy(item, 0, all, pos, item.length); + pos += item.length; + } + return all; + } + + private static @NonNull T[] createEmptyArray(Class kind) { + if (kind == String.class) { + return (T[]) EmptyArray.STRING; + } else if (kind == Object.class) { + return (T[]) EmptyArray.OBJECT; + } + + return (T[]) Array.newInstance(kind, 0); + } + + private static final class EmptyArray { + private EmptyArray() {} + + public static final Object[] OBJECT = new Object[0]; + public static final String[] STRING = new String[0]; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable char[] array, char value) { + if (array == null) return false; + for (char element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable Collection cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable int[] array, int value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable long[] array, long value) { + if (array == null) return false; + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Checks if {@code value} is in {@code array}. + */ + public static boolean contains(@Nullable T[] array, T value) { + return indexOf(array, value) != -1; + } + + /** + * Return first index of {@code value} in {@code array}, or {@code -1} if + * not found. + */ + public static int indexOf(@Nullable T[] array, T value) { + if (array == null) return -1; + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) return i; + } + return -1; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Collection array) { + return array == null || array.isEmpty(); + } + + /** + * Checks if given map is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Map map) { + return map == null || map.isEmpty(); + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable long[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable byte[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable boolean[] array) { + return array == null || array.length == 0; + } +} -- cgit v1.2.3 From 74cb7199641e4bc34358f70c608a10938464517b Mon Sep 17 00:00:00 2001 From: Artur Satayev Date: Tue, 10 Dec 2019 17:47:56 +0000 Subject: Use new UnsupportedAppUsage annotation. Existing annotations in libcore/ and frameworks/ will deleted after the migration. This also means that any java library that compiles @UnsupportedAppUsage requires a direct dependency on "unsupportedappusage" java_library. Bug: 145132366 Test: m && diff unsupportedappusage_index.csv Change-Id: I547d7fb2e6bc2e9707bbc0d14dc1e8cd632c5a23 --- telephony/common/com/android/internal/telephony/EncodeException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/EncodeException.java b/telephony/common/com/android/internal/telephony/EncodeException.java index cdc853e09895..bb723a0f47b3 100644 --- a/telephony/common/com/android/internal/telephony/EncodeException.java +++ b/telephony/common/com/android/internal/telephony/EncodeException.java @@ -16,7 +16,7 @@ package com.android.internal.telephony; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * {@hide} -- cgit v1.2.3 From 2c3caed0366ad2475713bbe36d9597443a820afa Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Tue, 7 Jan 2020 16:46:29 -0800 Subject: [Telephony Mainline] Move TelephonyUtils to telephony/common Bug: 140908357 Test: Build Change-Id: I94764fcf698477285ea366c5c7e90e7bd8a27eb3 --- .../internal/telephony/util/TelephonyUtils.java | 140 +++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 telephony/common/com/android/internal/telephony/util/TelephonyUtils.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java new file mode 100644 index 000000000000..2abcc76fdccc --- /dev/null +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 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.internal.telephony.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Binder; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.os.SystemProperties; + +import java.io.PrintWriter; +import java.util.function.Supplier; + +/** + * This class provides various util functions + */ +public final class TelephonyUtils { + public static boolean IS_USER = "user".equals(android.os.Build.TYPE); + public static boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1; + + /** + * Verify that caller holds {@link android.Manifest.permission#DUMP}. + * + * @return true if access should be granted. + */ + public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) { + if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump " + tag + " from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " due to missing android.permission.DUMP permission"); + return false; + } else { + return true; + } + } + + /** Returns an empty string if the input is {@code null}. */ + public static String emptyIfNull(@Nullable String str) { + return str == null ? "" : str; + } + + /** Throws a {@link RuntimeException} that wrapps the {@link RemoteException}. */ + public static RuntimeException rethrowAsRuntimeException(RemoteException remoteException) { + throw new RuntimeException(remoteException); + } + + /** + * Returns a {@link ComponentInfo} from the {@link ResolveInfo}, + * or throws an {@link IllegalStateException} if not available. + */ + public static ComponentInfo getComponentInfo(@NonNull ResolveInfo resolveInfo) { + if (resolveInfo.activityInfo != null) return resolveInfo.activityInfo; + if (resolveInfo.serviceInfo != null) return resolveInfo.serviceInfo; + if (resolveInfo.providerInfo != null) return resolveInfo.providerInfo; + throw new IllegalStateException("Missing ComponentInfo!"); + } + + /** + * Convenience method for running the provided action enclosed in + * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} + * + * Any exception thrown by the given action will need to be handled by caller. + * + */ + public static void runWithCleanCallingIdentity( + @NonNull Runnable action) { + long callingIdentity = Binder.clearCallingIdentity(); + try { + action.run(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + + /** + * Convenience method for running the provided action enclosed in + * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} and return + * the result. + * + * Any exception thrown by the given action will need to be handled by caller. + * + */ + public static T runWithCleanCallingIdentity( + @NonNull Supplier action) { + long callingIdentity = Binder.clearCallingIdentity(); + try { + return action.get(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + /** + * Filter values in bundle to only basic types. + */ + public static Bundle filterValues(Bundle bundle) { + Bundle ret = new Bundle(bundle); + for (String key : bundle.keySet()) { + Object value = bundle.get(key); + if ((value instanceof Integer) || (value instanceof Long) + || (value instanceof Double) || (value instanceof String) + || (value instanceof int[]) || (value instanceof long[]) + || (value instanceof double[]) || (value instanceof String[]) + || (value instanceof PersistableBundle) || (value == null) + || (value instanceof Boolean) || (value instanceof boolean[])) { + continue; + } + if (value instanceof Bundle) { + ret.putBundle(key, filterValues((Bundle) value)); + continue; + } + if (value.getClass().getName().startsWith("android.")) { + continue; + } + ret.remove(key); + } + return ret; + } +} -- cgit v1.2.3 From 2ebb31c00c31d2e44d972f178e537185c4c6aa5e Mon Sep 17 00:00:00 2001 From: Artur Satayev Date: Wed, 8 Jan 2020 12:24:36 +0000 Subject: Use new UnsupportedAppUsage annotation. Existing annotations in libcore/ and frameworks/ will deleted after the migration. This also means that any java library that compiles @UnsupportedAppUsage requires a direct dependency on "unsupportedappusage" java_library. Bug: 145132366 Test: m && diff unsupportedappusage_index.csv \ git diff HEAD^ HEAD | grep '^[+-][^+-]' | grep -v '.import' Change-Id: I853372f3c6fef905553bb31be4f1bb48df735f7a --- telephony/common/com/android/internal/telephony/GsmAlphabet.java | 2 +- telephony/common/com/android/internal/telephony/SmsConstants.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index 5fb4e90b9666..22cbdaa0f133 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -16,7 +16,7 @@ package com.android.internal.telephony; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; import android.telephony.Rlog; diff --git a/telephony/common/com/android/internal/telephony/SmsConstants.java b/telephony/common/com/android/internal/telephony/SmsConstants.java index 19f52b0ef429..3aa8bbf607d1 100644 --- a/telephony/common/com/android/internal/telephony/SmsConstants.java +++ b/telephony/common/com/android/internal/telephony/SmsConstants.java @@ -15,7 +15,7 @@ */ package com.android.internal.telephony; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * SMS Constants and must be the same as the corresponding -- cgit v1.2.3 From b8ffe81593804fd188f05345ca44d63887e90ba7 Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Wed, 8 Jan 2020 18:03:33 -0800 Subject: Use com.android.telephony.Rlog for mainline module. Bug: 144374158 Test: make Change-Id: I86a479affb8e0fa8e48734324a6a05c68230d910 --- telephony/common/android/telephony/LocationAccessPolicy.java | 2 ++ telephony/common/com/android/internal/telephony/CarrierAppUtils.java | 2 +- telephony/common/com/android/internal/telephony/GsmAlphabet.java | 2 +- telephony/common/com/android/internal/telephony/HbpcdUtils.java | 2 +- telephony/common/com/android/internal/telephony/SmsApplication.java | 2 +- telephony/common/com/android/internal/telephony/SmsNumberUtils.java | 2 +- .../common/com/android/internal/telephony/TelephonyPermissions.java | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f39981fdf25d..aaafee29e24a 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index eb02ea6f5e40..368f8f1dab2e 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -25,7 +25,7 @@ import android.content.res.Resources; import android.os.RemoteException; import android.permission.IPermissionManager; import android.provider.Settings; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index 5fb4e90b9666..6446ad04c8b0 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import android.util.SparseIntArray; diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 2f3194214be6..45a563c09394 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index b30258906368..afb9b6f52bdb 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c08f56aa1f..06c37288a1a6 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,7 +24,7 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 80a55b2a1147..4109ca6bd7d0 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,7 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; -- cgit v1.2.3 From b46d671110d4e19e15d5f42f071ad837c9997579 Mon Sep 17 00:00:00 2001 From: Sooraj Sasindran Date: Mon, 6 Jan 2020 11:20:20 -0800 Subject: Do not use hidden putIntForUser Do not use hidden Secure;->getIntForUser Secure;->putIntForUser Bug: 146354533 Test: unit test com.android.frameworks.telephonytests (25 Tests) [1/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_EmptyList: PASSED (379ms) [2/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Associated_Default: PASSED (77ms) [3/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Associated_DisabledUntilUsed: PASSED (0ms) [4/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Disabled: PASSED (26ms) [5/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_DisabledUser: PASSED (25ms) [6/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Enabled: PASSED (51ms) [7/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_MissingAssociated_Default: PASSED (26ms) [8/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_UpdatedApp: PASSED (26ms) [9/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_UpdatedAssociated_DisabledUntilUsed: PASSED (51ms) [10/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_MissingApp: PASSED (50ms) [11/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default: PASSED (1ms) [12/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default_AlreadyRun: PASSED (51ms) [13/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Disabled: PASSED (0ms) [14/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_DisabledUntilUsed: PASSED (26ms) [15/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_DisabledUser: PASSED (25ms) [16/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Enabled: PASSED (51ms) [17/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_EnabledAssociated_Default: PASSED (26ms) [18/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_UpdatedApp: PASSED (25ms) [19/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NonSystemApp: PASSED (26ms) [20/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_Default: PASSED (26ms) [21/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_Disabled: PASSED (25ms) [22/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_DisabledUntilUsed: PASSED (26ms) [23/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_DisabledUser: PASSED (25ms) [24/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_Enabled: PASSED (26ms) [25/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_UpdatedApp: PASSED (26ms) Change-Id: Ide97d443f759ee60a41ba55096b6f9769c6eea3a --- .../internal/telephony/CarrierAppUtils.java | 25 +++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index eb02ea6f5e40..eee844b50605 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -18,11 +18,13 @@ package com.android.internal.telephony; import android.annotation.Nullable; import android.content.ContentResolver; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.RemoteException; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; import android.telephony.Rlog; @@ -76,7 +78,7 @@ public final class CarrierAppUtils { */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, - TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) { + TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -85,6 +87,7 @@ public final class CarrierAppUtils { config.getDisabledUntilUsedPreinstalledCarrierApps(); ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); @@ -102,8 +105,8 @@ public final class CarrierAppUtils { * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, - ContentResolver contentResolver, int userId) { + IPackageManager packageManager, IPermissionManager permissionManager, int userId, + Context context) { if (DEBUG) { Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -114,15 +117,23 @@ public final class CarrierAppUtils { ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, null /* telephonyManager */, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); } + private static ContentResolver getContentResolverForUser(Context context, int userId) { + Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId), + 0); + return userContext.getContentResolver(); + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. */ + // Must be public b/c framework unit tests can't access package-private methods. @VisibleForTesting public static void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, @@ -142,9 +153,8 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed); List enabledCarrierPackages = new ArrayList<>(); - - boolean hasRunOnce = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1; + boolean hasRunOnce = Settings.Secure.getInt(contentResolver, + Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1; try { for (ApplicationInfo ai : candidates) { @@ -259,8 +269,7 @@ public final class CarrierAppUtils { // Mark the execution so we do not disable apps again. if (!hasRunOnce) { - Settings.Secure.putIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId); + Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1); } if (!enabledCarrierPackages.isEmpty()) { -- cgit v1.2.3 From 1b3e4e06049c08edd08347e6782d0e5b56938271 Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Fri, 10 Jan 2020 10:36:21 -0800 Subject: telephony/common/* should use android.util.Log Bug: 144374158 Test: make Change-Id: Iec082f0c0256d4e8f9cdbb2ebaf7425227f2299c --- .../android/telephony/LocationAccessPolicy.java | 2 - .../internal/telephony/CarrierAppUtils.java | 18 +++--- .../android/internal/telephony/GsmAlphabet.java | 22 +++---- .../com/android/internal/telephony/HbpcdUtils.java | 32 +++++----- .../android/internal/telephony/SmsApplication.java | 12 ++-- .../android/internal/telephony/SmsNumberUtils.java | 69 ++++++++++++++++++---- .../internal/telephony/TelephonyPermissions.java | 9 ++- 7 files changed, 102 insertions(+), 62 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index aaafee29e24a..f39981fdf25d 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 97bcbc061f8a..a17a19c74e08 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -27,7 +27,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import com.android.telephony.Rlog; +import android.util.Log; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; @@ -80,7 +80,7 @@ public final class CarrierAppUtils { IPackageManager packageManager, IPermissionManager permissionManager, TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { - Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } SystemConfig config = SystemConfig.getInstance(); ArraySet systemCarrierAppsDisabledUntilUsed = @@ -108,7 +108,7 @@ public final class CarrierAppUtils { IPackageManager packageManager, IPermissionManager permissionManager, int userId, Context context) { if (DEBUG) { - Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } SystemConfig config = SystemConfig.getInstance(); ArraySet systemCarrierAppsDisabledUntilUsed = @@ -187,7 +187,7 @@ public final class CarrierAppUtils { || ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { - Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user " + Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( packageName, @@ -210,7 +210,7 @@ public final class CarrierAppUtils { == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - Rlog.i(TAG, "Update associated state(" + associatedApp.packageName + Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( associatedApp.packageName, @@ -235,7 +235,7 @@ public final class CarrierAppUtils { && ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Rlog.i(TAG, "Update state(" + packageName + Log.i(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED for user " + userId); packageManager.setSystemAppInstallState( packageName, @@ -253,7 +253,7 @@ public final class CarrierAppUtils { == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Rlog.i(TAG, + Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): DISABLED_UNTIL_USED for user " + userId); packageManager.setSystemAppInstallState( @@ -280,7 +280,7 @@ public final class CarrierAppUtils { permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); } } catch (RemoteException e) { - Rlog.w(TAG, "Could not reach PackageManager", e); + Log.w(TAG, "Could not reach PackageManager", e); } } @@ -402,7 +402,7 @@ public final class CarrierAppUtils { return ai; } } catch (RemoteException e) { - Rlog.w(TAG, "Could not reach PackageManager", e); + Log.w(TAG, "Could not reach PackageManager", e); } return null; } diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index a36ff9341275..5c53f7e5a4d0 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import com.android.telephony.Rlog; +import android.util.Log; import android.text.TextUtils; import android.util.SparseIntArray; @@ -498,11 +498,11 @@ public class GsmAlphabet { StringBuilder ret = new StringBuilder(lengthSeptets); if (languageTable < 0 || languageTable > sLanguageTables.length) { - Rlog.w(TAG, "unknown language table " + languageTable + ", using default"); + Log.w(TAG, "unknown language table " + languageTable + ", using default"); languageTable = 0; } if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) { - Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default"); + Log.w(TAG, "unknown single shift table " + shiftTable + ", using default"); shiftTable = 0; } @@ -512,11 +512,11 @@ public class GsmAlphabet { String shiftTableToChar = sLanguageShiftTables[shiftTable]; if (languageTableToChar.isEmpty()) { - Rlog.w(TAG, "no language table for code " + languageTable + ", using default"); + Log.w(TAG, "no language table for code " + languageTable + ", using default"); languageTableToChar = sLanguageTables[0]; } if (shiftTableToChar.isEmpty()) { - Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default"); + Log.w(TAG, "no single shift table for code " + shiftTable + ", using default"); shiftTableToChar = sLanguageShiftTables[0]; } @@ -556,7 +556,7 @@ public class GsmAlphabet { } } } catch (RuntimeException ex) { - Rlog.e(TAG, "Error GSM 7 bit packed: ", ex); + Log.e(TAG, "Error GSM 7 bit packed: ", ex); return null; } @@ -813,7 +813,7 @@ public class GsmAlphabet { for (int i = 0; i < sz; i++) { char c = s.charAt(i); if (c == GSM_EXTENDED_ESCAPE) { - Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping."); + Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping."); continue; } if (charToLanguageTable.get(c, -1) != -1) { @@ -892,7 +892,7 @@ public class GsmAlphabet { for (int i = 0; i < sz && !lpcList.isEmpty(); i++) { char c = s.charAt(i); if (c == GSM_EXTENDED_ESCAPE) { - Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!"); + Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!"); continue; } // iterate through enabled locking shift tables @@ -1496,7 +1496,7 @@ public class GsmAlphabet { int numTables = sLanguageTables.length; int numShiftTables = sLanguageShiftTables.length; if (numTables != numShiftTables) { - Rlog.e(TAG, "Error: language tables array length " + numTables + + Log.e(TAG, "Error: language tables array length " + numTables + " != shift tables array length " + numShiftTables); } @@ -1506,7 +1506,7 @@ public class GsmAlphabet { int tableLen = table.length(); if (tableLen != 0 && tableLen != 128) { - Rlog.e(TAG, "Error: language tables index " + i + + Log.e(TAG, "Error: language tables index " + i + " length " + tableLen + " (expected 128 or 0)"); } @@ -1524,7 +1524,7 @@ public class GsmAlphabet { int shiftTableLen = shiftTable.length(); if (shiftTableLen != 0 && shiftTableLen != 128) { - Rlog.e(TAG, "Error: language shift tables index " + i + + Log.e(TAG, "Error: language shift tables index " + i + " length " + shiftTableLen + " (expected 128 or 0)"); } diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 45a563c09394..714f5a673633 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import com.android.telephony.Rlog; +import android.util.Log; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; @@ -54,16 +54,16 @@ public final class HbpcdUtils { if (c2 != null) { int c2Counter = c2.getCount(); if (DBG) { - Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); + Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); } if (c2Counter == 1) { if (DBG) { - Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); + Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); } c2.moveToFirst(); tmpMcc = c2.getInt(0); if (DBG) { - Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); + Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); } c2.close(); return tmpMcc; @@ -85,18 +85,18 @@ public final class HbpcdUtils { int c3Counter = c3.getCount(); if (c3Counter > 0) { if (c3Counter > 1) { - Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); + Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); } - if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); + if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); c3.moveToFirst(); tmpMcc = c3.getInt(0); if (DBG) { - Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); + Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); } if (!isNitzTimeZone) { // time zone is not accurate, it may get wrong mcc, ignore it. if (DBG) { - Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); + Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); } tmpMcc = 0; } @@ -115,18 +115,18 @@ public final class HbpcdUtils { null, null); if (c5 != null) { if (c5.getCount() > 0) { - if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5); + if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5); c5.moveToFirst(); tmpMcc = c5.getInt(0); - if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); + if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); c5.close(); return tmpMcc; } c5.close(); } - if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range."); + if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range."); - if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); + if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); // If unknown MCC still could not be resolved, return tmpMcc; } @@ -135,7 +135,7 @@ public final class HbpcdUtils { * Gets country information with given MCC. */ public String getIddByMcc(int mcc) { - if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); + if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); String idd = ""; Cursor c = null; @@ -145,19 +145,19 @@ public final class HbpcdUtils { MccIdd.MCC + "=" + mcc, null, null); if (cur != null) { if (cur.getCount() > 0) { - if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur); + if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur); // TODO: for those country having more than 1 IDDs, need more information // to decide which IDD would be used. currently just use the first 1. cur.moveToFirst(); idd = cur.getString(0); - if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd); + if (DBG) Log.d(LOG_TAG, "IDD = " + idd); } cur.close(); } if (c != null) c.close(); - if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); + if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); return idd; } } diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index afb9b6f52bdb..9b8282806c3c 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import com.android.telephony.Rlog; +import android.util.Log; import android.telephony.TelephonyManager; import android.util.Log; @@ -565,7 +565,7 @@ public final class SmsApplication { int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid, applicationData.mPackageName); if (mode != AppOpsManager.MODE_ALLOWED) { - Rlog.e(LOG_TAG, applicationData.mPackageName + " lost " + Log.e(LOG_TAG, applicationData.mPackageName + " lost " + opStr + ": " + (updateIfNeeded ? " (fixing)" : " (no permission to fix)")); if (updateIfNeeded) { @@ -643,7 +643,7 @@ public final class SmsApplication { int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid; setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT); } catch (NameNotFoundException e) { - Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); + Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); } } @@ -750,7 +750,7 @@ public final class SmsApplication { // the package signature matches system signature. final int result = packageManager.checkSignatures(context.getPackageName(), packageName); if (result != PackageManager.SIGNATURE_MATCH) { - Rlog.e(LOG_TAG, packageName + " does not have system signature"); + Log.e(LOG_TAG, packageName + " does not have system signature"); return; } try { @@ -758,13 +758,13 @@ public final class SmsApplication { int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { - Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); + Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); setExclusiveAppops(packageName, appOps, info.applicationInfo.uid, AppOpsManager.MODE_ALLOWED); } } catch (NameNotFoundException e) { // No whitelisted system app on this device - Rlog.e(LOG_TAG, "Package not found: " + packageName); + Log.e(LOG_TAG, "Package not found: " + packageName); } } diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c37288a1a6..cd365a113189 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,13 +24,16 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; import com.android.internal.telephony.HbpcdLookup.MccIdd; import com.android.internal.telephony.HbpcdLookup.MccLookup; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; @@ -143,7 +146,7 @@ public class SmsNumberUtils { // First check whether the number is a NANP number. int nanpState = checkNANP(numberEntry, allIDDs); - if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); + if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); if ((nanpState == NP_NANP_LOCAL) || (nanpState == NP_NANP_AREA_LOCAL) @@ -173,7 +176,7 @@ public class SmsNumberUtils { int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs, NANP_IDD); - if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState)); + if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState)); String returnNumber = null; switch (internationalState) { @@ -272,7 +275,7 @@ public class SmsNumberUtils { } } } catch (SQLException e) { - Rlog.e(TAG, "Can't access HbpcdLookup database", e); + Log.e(TAG, "Can't access HbpcdLookup database", e); } finally { if (cursor != null) { cursor.close(); @@ -281,7 +284,7 @@ public class SmsNumberUtils { IDDS_MAPS.put(mcc, allIDDs); - if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); + if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); return allIDDs; } @@ -472,7 +475,7 @@ public class SmsNumberUtils { int tempCC = allCCs[i]; for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) { if (tempCC == ccArray[j]) { - if (DBG) Rlog.d(TAG, "Country code = " + tempCC); + if (DBG) Log.d(TAG, "Country code = " + tempCC); return tempCC; } } @@ -509,7 +512,7 @@ public class SmsNumberUtils { } } } catch (SQLException e) { - Rlog.e(TAG, "Can't access HbpcdLookup database", e); + Log.e(TAG, "Can't access HbpcdLookup database", e); } finally { if (cursor != null) { cursor.close(); @@ -561,10 +564,10 @@ public class SmsNumberUtils { * Filter the destination number if using VZW sim card. */ public static String filterDestAddr(Context context, int subId, String destAddr) { - if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" ); + if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" ); if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) { - Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) + + Log.w(TAG, "destAddr" + pii(TAG, destAddr) + " is not a global phone number! Nothing changed."); return destAddr; } @@ -585,9 +588,9 @@ public class SmsNumberUtils { } if (DBG) { - Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); - Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG, - result) : Rlog.pii(TAG, destAddr)) + "\""); + Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); + Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG, + result) : pii(TAG, destAddr)) + "\""); } return result != null ? result : destAddr; } @@ -608,7 +611,7 @@ public class SmsNumberUtils { networkType = CDMA_HOME_NETWORK; } } else { - if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType); + if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType); } return networkType; @@ -650,4 +653,44 @@ public class SmsNumberUtils { // by default this value is false return false; } + + /** + * Redact personally identifiable information for production users. + * @param tag used to identify the source of a log message + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If tag is loggable in verbose mode or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + private static String pii(String tag, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Returns a secure hash (using the SHA1 algorithm) of the provided input. + * + * @return "****" if the build type is user, otherwise the hash + * @param input the bytes for which the secure hash should be computed. + */ + private static String secureHash(byte[] input) { + // Refrain from logging user personal information in user build. + if (android.os.Build.IS_USER) { + return "****"; + } + + MessageDigest messageDigest; + + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return "####"; + } + + byte[] result = messageDigest.digest(input); + return Base64.encodeToString( + result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } } diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 4109ca6bd7d0..f6ce0dc827d8 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,6 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; @@ -521,7 +520,7 @@ public final class TelephonyPermissions { return; } - if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next."); + if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next."); enforceCallingOrSelfCarrierPrivilege(context, subId, message); } @@ -539,7 +538,7 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); + Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); } enforceCallingOrSelfCarrierPrivilege(context, subId, message); @@ -559,7 +558,7 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + Log.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + "check carrier privilege next."); } @@ -584,7 +583,7 @@ public final class TelephonyPermissions { Context context, int subId, int uid, String message) { if (getCarrierPrivilegeStatus(context, subId, uid) != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); + if (DBG) Log.e(LOG_TAG, "No Carrier Privilege."); throw new SecurityException(message); } } -- cgit v1.2.3 From 07f4639c7c972b84b5db0d12d1a1c0ab3285b63a Mon Sep 17 00:00:00 2001 From: Sooraj Sasindran Date: Mon, 16 Dec 2019 10:23:57 -0800 Subject: Do not use hidden enabledSetting Do not use hidden ApplicationInfo#enabledSetting Bug: 140908357 Test: unit test Test: unit test om.android.frameworks.telephonytests (25 Tests) [1/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_EmptyList: PASSED (379ms) [2/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Associated_Default: PASSED (25ms) [3/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Associated_DisabledUntilUsed: PASSED (1ms) [4/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Disabled: PASSED (25ms) [5/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_DisabledUser: PASSED (76ms) [6/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_Enabled: PASSED (26ms) [7/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_MissingAssociated_Default: PASSED (25ms) [8/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_UpdatedApp: PASSED (25ms) [9/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_HasPrivileges_UpdatedAssociated_DisabledUntilUsed: PASSED (26ms) [10/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_MissingApp: PASSED (26ms) [11/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default: PASSED (25ms) [12/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default_AlreadyRun: PASSED (26ms) [13/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Disabled: PASSED (25ms) [14/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_DisabledUntilUsed: PASSED (51ms) [15/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_DisabledUser: PASSED (26ms) [16/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_Enabled: PASSED (50ms) [17/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_EnabledAssociated_Default: PASSED (26ms) [18/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NoPrivileges_UpdatedApp: PASSED (25ms) [19/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NonSystemApp: PASSED (26ms) [20/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_Default: PASSED (26ms) [21/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_Disabled: PASSED (26ms) [22/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_DisabledUntilUsed: PASSED (26ms) [23/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_DisabledUser: PASSED (26ms) [24/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_Enabled: PASSED (26ms) [25/25] com.android.internal.telephony.CarrierAppUtilsTest#testDisableCarrierAppsUntilPrivileged_NullPrivileges_UpdatedApp: PASSED (25ms) Summary ------- arm64-v8a FrameworksTelephonyTests: Passed: 25, Failed: 0, Ignored: 0, Assumption Failed: 0 Change-Id: I25d6c7ae0416dd96bf66dbd1615fba5ec87f80cf --- .../internal/telephony/CarrierAppUtils.java | 84 +++++++++++++++++----- 1 file changed, 68 insertions(+), 16 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index a17a19c74e08..9bc534c2877a 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -27,10 +27,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import android.util.Log; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -141,8 +141,8 @@ public final class CarrierAppUtils { ContentResolver contentResolver, int userId, ArraySet systemCarrierAppsDisabledUntilUsed, ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { - List candidates = getDefaultCarrierAppCandidatesHelper(packageManager, - userId, systemCarrierAppsDisabledUntilUsed); + List candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + packageManager, userId, systemCarrierAppsDisabledUntilUsed); if (candidates == null || candidates.isEmpty()) { return; } @@ -178,15 +178,16 @@ public final class CarrierAppUtils { } } + int enabledSetting = packageManager.getApplicationEnabledSetting(packageName, + userId); if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && (ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || ai.enabledSetting + || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED - || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { + || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( @@ -204,9 +205,12 @@ public final class CarrierAppUtils { // Also enable any associated apps for this carrier app. if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || associatedApp.enabledSetting + || associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { @@ -231,8 +235,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -249,7 +252,10 @@ public final class CarrierAppUtils { if (!hasRunOnce) { if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { @@ -360,6 +366,31 @@ public final class CarrierAppUtils { return apps; } + private static List getDefaultNotUpdatedCarrierAppCandidatesHelper( + IPackageManager packageManager, + int userId, + ArraySet systemCarrierAppsDisabledUntilUsed) { + if (systemCarrierAppsDisabledUntilUsed == null) { + return null; + } + + int size = systemCarrierAppsDisabledUntilUsed.size(); + if (size == 0) { + return null; + } + + List apps = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + ApplicationInfo ai = + getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName); + if (ai != null) { + apps.add(ai); + } + } + return apps; + } + private static Map> getDefaultCarrierAssociatedAppsHelper( IPackageManager packageManager, int userId, @@ -372,11 +403,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfSystemApp( + getApplicationInfoIfNotUpdatedSystemApp( packageManager, userId, associatedAppPackages.get(j)); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null && !ai.isUpdatedSystemApp()) { + if (ai != null) { List appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -389,6 +420,26 @@ public final class CarrierAppUtils { return associatedApps; } + @Nullable + private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( + IPackageManager packageManager, + int userId, + String packageName) { + try { + ApplicationInfo ai = packageManager.getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_FACTORY_ONLY, userId); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + Log.w(TAG, "Could not reach PackageManager", e); + } + return null; + } + @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( IPackageManager packageManager, @@ -397,8 +448,9 @@ public final class CarrierAppUtils { try { ApplicationInfo ai = packageManager.getApplicationInfo(packageName, PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); - if (ai != null && ai.isSystemApp()) { + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY, userId); + if (ai != null) { return ai; } } catch (RemoteException e) { -- cgit v1.2.3 From f7eb0fa7976715e72bf4d9bdcf95ea1bda346cdc Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Tue, 29 Oct 2019 15:56:31 -0700 Subject: [Telephony Mainline] Refactored PermissionManager to expose grantDefaultPermissionsToEnabledImsServices,grantDefaultPermissionsToEnabledTelephonyDataServices,revokeDefaultPermissionsFromDisabledTelephonyDataServices Bug: 138745534 Test: Build and ImsServiceControllerTest Change-Id: I9a7cf7a3a960e23fc943f58a67baed354c0d2a92 --- .../com/android/internal/telephony/util/TelephonyUtils.java | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index 2abcc76fdccc..a7ad884ca107 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -28,6 +28,8 @@ import android.os.RemoteException; import android.os.SystemProperties; import java.io.PrintWriter; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** @@ -137,4 +139,12 @@ public final class TelephonyUtils { } return ret; } + + /** Wait for latch to trigger */ + public static void waitUntilReady(CountDownLatch latch, long timeoutMs) { + try { + latch.await(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { + } + } } -- cgit v1.2.3 From 546ce5e45eadc6053d41545315f1ea6ca9a00ad4 Mon Sep 17 00:00:00 2001 From: Chen Xu Date: Fri, 10 Jan 2020 08:38:31 -0800 Subject: modularize telephony-framework from framework.jar Bug: 140908357 Test: Build & Manaul Change-Id: Idd4cbdb84247b98072b6ebc00a37168fde6fbbaa --- .../common/com/android/internal/telephony/TelephonyPermissions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index f6ce0dc827d8..b8b203def6b8 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -592,7 +592,7 @@ public final class TelephonyPermissions { private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) { SubscriptionManager sm = (SubscriptionManager) context.getSystemService( Context.TELEPHONY_SUBSCRIPTION_SERVICE); - int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false); + int[] activeSubIds = sm.getActiveAndHiddenSubscriptionIdList(); for (int activeSubId : activeSubIds) { if (getCarrierPrivilegeStatus(context, activeSubId, uid) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { -- cgit v1.2.3 From 15b4c2602404cb444bc9ef29dd6de34ea15aef5e Mon Sep 17 00:00:00 2001 From: Grace Jia Date: Tue, 7 Jan 2020 14:25:24 -0800 Subject: Fix hidden API usages in LocationAccessPolicy and add API in ActivityManager Bug: 146355892 Test: Cts test, atest TeleServiceTests Change-Id: I91bf8a05383f29feaa26b03d6a03ed7de2d79061 --- .../android/telephony/LocationAccessPolicy.java | 25 ++++++++-------------- 1 file changed, 9 insertions(+), 16 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f39981fdf25d..f3e9de0d2688 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -24,20 +24,16 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; import android.util.Log; import android.widget.Toast; import com.android.internal.telephony.util.TelephonyUtils; -import java.util.List; - /** * Helper for performing location access checks. * @hide @@ -309,7 +305,7 @@ public final class LocationAccessPolicy { } private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { - if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { + if (!isLocationModeEnabled(context, UserHandle.getUserHandleForUid(uid).getIdentifier())) { if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); return false; } @@ -336,20 +332,17 @@ public final class LocationAccessPolicy { private static boolean isCurrentProfile(@NonNull Context context, int uid) { long token = Binder.clearCallingIdentity(); try { - final int currentUser = ActivityManager.getCurrentUser(); - final int callingUserId = UserHandle.getUserId(uid); - if (callingUserId == currentUser) { + if (UserHandle.getUserHandleForUid(uid).getIdentifier() + == ActivityManager.getCurrentUser()) { return true; + } + ActivityManager activityManager = context.getSystemService(ActivityManager.class); + if (activityManager != null) { + return activityManager.isProfileForeground( + UserHandle.getUserHandleForUid(ActivityManager.getCurrentUser())); } else { - List userProfiles = context.getSystemService( - UserManager.class).getProfiles(currentUser); - for (UserInfo user : userProfiles) { - if (user.id == callingUserId) { - return true; - } - } + return false; } - return false; } finally { Binder.restoreCallingIdentity(token); } -- cgit v1.2.3 From 6b06a38e3aa602f879cd17432b08c45af343a222 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Wed, 22 Jan 2020 15:54:47 -0800 Subject: Expose OPSTR_READ_DEVICE_IDENTIFIERS Expose the constant for use in Telephony. Also replace a usage of a hidden UserHandle API with a systemapi equivalent. Bug: 146834818 Test: atest SmsApplicationTests Change-Id: I569d921abf923c384d7e771e2fc28f54e0dbfdd1 --- telephony/common/com/android/internal/telephony/SmsApplication.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 9b8282806c3c..ffb3cb151ae4 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -205,7 +205,7 @@ public final class SmsApplication { < android.os.Process.FIRST_APPLICATION_UID) { return contextUserId; } else { - return UserHandle.getUserId(callingUid); + return UserHandle.getUserHandleForUid(callingUid).getIdentifier(); } } @@ -811,10 +811,10 @@ public final class SmsApplication { // This should never happen in prod -- unit tests will put the receiver into a // unusual state where the pending result is null, which produces a NPE when calling // getSendingUserId. Just pretend like it's the system user for testing. - userId = UserHandle.USER_SYSTEM; + userId = UserHandle.SYSTEM.getIdentifier(); } Context userContext = mContext; - if (userId != UserHandle.USER_SYSTEM) { + if (userId != UserHandle.SYSTEM.getIdentifier()) { try { userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, UserHandle.of(userId)); -- cgit v1.2.3 From a63e69cf94e5c6ea9de5eb312d36c96818711a2d Mon Sep 17 00:00:00 2001 From: Sarah Chin Date: Wed, 22 Jan 2020 15:41:53 -0800 Subject: Expose hidden resources used in telephony/common Test: atest GsmAlphabetTest, CarrierAppUtilsTest Bug: 148174114 Change-Id: Iff28c13e3470ea9b2e03cba33bf9489a089f8add --- .../common/com/android/internal/telephony/CarrierAppUtils.java | 3 +-- .../common/com/android/internal/telephony/GsmAlphabet.java | 10 +++++----- .../common/com/google/android/mms/util/SqliteWrapper.java | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 9bc534c2877a..56c342ee72ca 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -32,7 +32,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.util.ArrayUtils; import com.android.server.SystemConfig; @@ -160,7 +159,7 @@ public final class CarrierAppUtils { for (ApplicationInfo ai : candidates) { String packageName = ai.packageName; String[] restrictedCarrierApps = Resources.getSystem().getStringArray( - R.array.config_restrictedPreinstalledCarrierApps); + android.R.array.config_restrictedPreinstalledCarrierApps); boolean hasPrivileges = telephonyManager != null && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index 5c53f7e5a4d0..c62cec270440 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,12 +19,10 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import android.util.Log; import android.text.TextUtils; +import android.util.Log; import android.util.SparseIntArray; -import com.android.internal.R; - import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; @@ -1089,8 +1087,10 @@ public class GsmAlphabet { private static void enableCountrySpecificEncodings() { Resources r = Resources.getSystem(); // See comments in frameworks/base/core/res/res/values/config.xml for allowed values - sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables); - sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables); + sEnabledSingleShiftTables = r.getIntArray( + android.R.array.config_sms_enabled_single_shift_tables); + sEnabledLockingShiftTables = r.getIntArray( + android.R.array.config_sms_enabled_locking_shift_tables); if (sEnabledSingleShiftTables.length > 0) { sHighestEnabledSingleShiftCode = diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java index 2dd1dc11c2a9..d030246ed32a 100644 --- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java +++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java @@ -61,8 +61,7 @@ public final class SqliteWrapper { @UnsupportedAppUsage public static void checkSQLiteException(Context context, SQLiteException e) { if (isLowMemory(e)) { - Toast.makeText(context, com.android.internal.R.string.low_memory, - Toast.LENGTH_SHORT).show(); + Toast.makeText(context, android.R.string.low_memory, Toast.LENGTH_SHORT).show(); } else { throw e; } -- cgit v1.2.3 From 9a5c3105dd241e014d4601b00786897f7c5d1d8d Mon Sep 17 00:00:00 2001 From: Artur Satayev Date: Thu, 23 Jan 2020 16:49:36 +0000 Subject: Replace dalvik..UnsupportedAppUsage annotation. The new annotation to be used is android.compat.annotation.UnsupportedAppUsage. Test: m Bug: 145132366 Change-Id: Ib2101605fb385b4f778893e5181a954dccbea037 Exempt-From-Owner-Approval: roll-forward previously approved change --- telephony/common/com/android/internal/telephony/SmsApplication.java | 4 +--- telephony/common/com/google/android/mms/ContentType.java | 2 +- .../common/com/google/android/mms/InvalidHeaderValueException.java | 2 +- telephony/common/com/google/android/mms/MmsException.java | 2 +- telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java | 2 +- telephony/common/com/google/android/mms/pdu/Base64.java | 2 +- telephony/common/com/google/android/mms/pdu/CharacterSets.java | 2 +- telephony/common/com/google/android/mms/pdu/DeliveryInd.java | 2 +- telephony/common/com/google/android/mms/pdu/EncodedStringValue.java | 3 +-- telephony/common/com/google/android/mms/pdu/GenericPdu.java | 2 +- telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java | 2 +- telephony/common/com/google/android/mms/pdu/NotificationInd.java | 2 +- telephony/common/com/google/android/mms/pdu/NotifyRespInd.java | 2 +- telephony/common/com/google/android/mms/pdu/PduBody.java | 2 +- telephony/common/com/google/android/mms/pdu/PduComposer.java | 3 +-- telephony/common/com/google/android/mms/pdu/PduContentTypes.java | 2 +- telephony/common/com/google/android/mms/pdu/PduHeaders.java | 2 +- telephony/common/com/google/android/mms/pdu/PduParser.java | 3 +-- telephony/common/com/google/android/mms/pdu/PduPart.java | 3 +-- telephony/common/com/google/android/mms/pdu/PduPersister.java | 3 +-- telephony/common/com/google/android/mms/pdu/QuotedPrintable.java | 2 +- telephony/common/com/google/android/mms/pdu/ReadOrigInd.java | 2 +- telephony/common/com/google/android/mms/pdu/ReadRecInd.java | 2 +- telephony/common/com/google/android/mms/pdu/RetrieveConf.java | 2 +- telephony/common/com/google/android/mms/pdu/SendConf.java | 2 +- telephony/common/com/google/android/mms/pdu/SendReq.java | 3 +-- telephony/common/com/google/android/mms/util/AbstractCache.java | 3 +-- telephony/common/com/google/android/mms/util/DownloadDrmHelper.java | 3 +-- telephony/common/com/google/android/mms/util/DrmConvertSession.java | 3 +-- telephony/common/com/google/android/mms/util/PduCache.java | 3 +-- telephony/common/com/google/android/mms/util/PduCacheEntry.java | 2 +- telephony/common/com/google/android/mms/util/SqliteWrapper.java | 3 +-- 32 files changed, 32 insertions(+), 45 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index ffb3cb151ae4..32f9d53e59f8 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -20,6 +20,7 @@ import android.Manifest.permission; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.role.RoleManager; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -40,7 +41,6 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import android.util.Log; import android.telephony.TelephonyManager; import android.util.Log; @@ -48,8 +48,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.util.Collection; import java.util.HashMap; import java.util.List; diff --git a/telephony/common/com/google/android/mms/ContentType.java b/telephony/common/com/google/android/mms/ContentType.java index 12e4b7e26e1e..4a971dd34c8f 100644 --- a/telephony/common/com/google/android/mms/ContentType.java +++ b/telephony/common/com/google/android/mms/ContentType.java @@ -17,7 +17,7 @@ package com.google.android.mms; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import java.util.ArrayList; diff --git a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java index 2836c3075b3b..55087ff0fb1d 100644 --- a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java +++ b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java @@ -17,7 +17,7 @@ package com.google.android.mms; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * Thrown when an invalid header value was set. diff --git a/telephony/common/com/google/android/mms/MmsException.java b/telephony/common/com/google/android/mms/MmsException.java index 5be33ed1fac9..24bceb37f590 100644 --- a/telephony/common/com/google/android/mms/MmsException.java +++ b/telephony/common/com/google/android/mms/MmsException.java @@ -17,7 +17,7 @@ package com.google.android.mms; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * A generic exception that is thrown by the Mms client. diff --git a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java index ae447d7a7417..8693385bb032 100644 --- a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java +++ b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/Base64.java b/telephony/common/com/google/android/mms/pdu/Base64.java index 483fa7f9842e..0d6a46a59fcc 100644 --- a/telephony/common/com/google/android/mms/pdu/Base64.java +++ b/telephony/common/com/google/android/mms/pdu/Base64.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class Base64 { /** diff --git a/telephony/common/com/google/android/mms/pdu/CharacterSets.java b/telephony/common/com/google/android/mms/pdu/CharacterSets.java index 27da35e2d928..5172b7b67f88 100644 --- a/telephony/common/com/google/android/mms/pdu/CharacterSets.java +++ b/telephony/common/com/google/android/mms/pdu/CharacterSets.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import java.io.UnsupportedEncodingException; import java.util.HashMap; diff --git a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java index 7093ac63338c..8fb6a7545abf 100644 --- a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java +++ b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java index 41662750842f..8c0380f77cdd 100644 --- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java +++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java @@ -17,10 +17,9 @@ package com.google.android.mms.pdu; +import android.compat.annotation.UnsupportedAppUsage; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; diff --git a/telephony/common/com/google/android/mms/pdu/GenericPdu.java b/telephony/common/com/google/android/mms/pdu/GenericPdu.java index ebf16ac7e632..320b13ffed2b 100644 --- a/telephony/common/com/google/android/mms/pdu/GenericPdu.java +++ b/telephony/common/com/google/android/mms/pdu/GenericPdu.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java index e108f7600baf..42a89c69e873 100644 --- a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java +++ b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/NotificationInd.java b/telephony/common/com/google/android/mms/pdu/NotificationInd.java index b561bd4ab3a7..ca4615c2e9fe 100644 --- a/telephony/common/com/google/android/mms/pdu/NotificationInd.java +++ b/telephony/common/com/google/android/mms/pdu/NotificationInd.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java index 3c70f86a0890..ebd81afc0173 100644 --- a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java +++ b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/PduBody.java b/telephony/common/com/google/android/mms/pdu/PduBody.java index 51914e4110b0..f7f285f653b9 100644 --- a/telephony/common/com/google/android/mms/pdu/PduBody.java +++ b/telephony/common/com/google/android/mms/pdu/PduBody.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import java.util.HashMap; import java.util.Map; diff --git a/telephony/common/com/google/android/mms/pdu/PduComposer.java b/telephony/common/com/google/android/mms/pdu/PduComposer.java index e24bf21a11b5..b8b212c493aa 100644 --- a/telephony/common/com/google/android/mms/pdu/PduComposer.java +++ b/telephony/common/com/google/android/mms/pdu/PduComposer.java @@ -17,12 +17,11 @@ package com.google.android.mms.pdu; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; import android.text.TextUtils; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java index 8551b2f9b693..57141fedf1e0 100644 --- a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java +++ b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class PduContentTypes { /** diff --git a/telephony/common/com/google/android/mms/pdu/PduHeaders.java b/telephony/common/com/google/android/mms/pdu/PduHeaders.java index b5244645fda1..3e6218480dc5 100644 --- a/telephony/common/com/google/android/mms/pdu/PduHeaders.java +++ b/telephony/common/com/google/android/mms/pdu/PduHeaders.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/PduParser.java b/telephony/common/com/google/android/mms/pdu/PduParser.java index f48399410723..5340245ae869 100755 --- a/telephony/common/com/google/android/mms/pdu/PduParser.java +++ b/telephony/common/com/google/android/mms/pdu/PduParser.java @@ -17,10 +17,9 @@ package com.google.android.mms.pdu; +import android.compat.annotation.UnsupportedAppUsage; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import com.google.android.mms.ContentType; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/PduPart.java b/telephony/common/com/google/android/mms/pdu/PduPart.java index 09b775118dc3..8dd976b2569f 100644 --- a/telephony/common/com/google/android/mms/pdu/PduPart.java +++ b/telephony/common/com/google/android/mms/pdu/PduPart.java @@ -17,10 +17,9 @@ package com.google.android.mms.pdu; +import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.util.HashMap; import java.util.Map; diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java index 8efca0ea3909..fcd5b8ff57a8 100755 --- a/telephony/common/com/google/android/mms/pdu/PduPersister.java +++ b/telephony/common/com/google/android/mms/pdu/PduPersister.java @@ -17,6 +17,7 @@ package com.google.android.mms.pdu; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -40,8 +41,6 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import com.google.android.mms.ContentType; import com.google.android.mms.InvalidHeaderValueException; import com.google.android.mms.MmsException; diff --git a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java index 9d6535c72e90..4e1d7f5775ec 100644 --- a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java +++ b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import java.io.ByteArrayOutputStream; diff --git a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java index e38c62dde622..4ba3c71580e0 100644 --- a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java +++ b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java index 9696bc259d00..37ccfb9c9b9b 100644 --- a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java +++ b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java index 03755af4189c..260adfc093f2 100644 --- a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java +++ b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/SendConf.java b/telephony/common/com/google/android/mms/pdu/SendConf.java index b85982791ada..779923801bfa 100644 --- a/telephony/common/com/google/android/mms/pdu/SendConf.java +++ b/telephony/common/com/google/android/mms/pdu/SendConf.java @@ -17,7 +17,7 @@ package com.google.android.mms.pdu; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.InvalidHeaderValueException; diff --git a/telephony/common/com/google/android/mms/pdu/SendReq.java b/telephony/common/com/google/android/mms/pdu/SendReq.java index c1b7f934c0f7..6e2f2da01791 100644 --- a/telephony/common/com/google/android/mms/pdu/SendReq.java +++ b/telephony/common/com/google/android/mms/pdu/SendReq.java @@ -17,10 +17,9 @@ package com.google.android.mms.pdu; +import android.compat.annotation.UnsupportedAppUsage; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import com.google.android.mms.InvalidHeaderValueException; public class SendReq extends MultimediaMessagePdu { diff --git a/telephony/common/com/google/android/mms/util/AbstractCache.java b/telephony/common/com/google/android/mms/util/AbstractCache.java index ab5d48a4ce3d..25862e73581e 100644 --- a/telephony/common/com/google/android/mms/util/AbstractCache.java +++ b/telephony/common/com/google/android/mms/util/AbstractCache.java @@ -17,10 +17,9 @@ package com.google.android.mms.util; +import android.compat.annotation.UnsupportedAppUsage; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.util.HashMap; public abstract class AbstractCache { diff --git a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java index 118de465a518..0f9390daa725 100644 --- a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java +++ b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java @@ -17,12 +17,11 @@ package com.google.android.mms.util; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.drm.DrmManagerClient; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - public class DownloadDrmHelper { private static final String TAG = "DownloadDrmHelper"; diff --git a/telephony/common/com/google/android/mms/util/DrmConvertSession.java b/telephony/common/com/google/android/mms/util/DrmConvertSession.java index 0e8ec91f4ef6..156c7ad8baac 100644 --- a/telephony/common/com/google/android/mms/util/DrmConvertSession.java +++ b/telephony/common/com/google/android/mms/util/DrmConvertSession.java @@ -16,14 +16,13 @@ */ package com.google.android.mms.util; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.drm.DrmConvertedStatus; import android.drm.DrmManagerClient; import android.provider.Downloads; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; diff --git a/telephony/common/com/google/android/mms/util/PduCache.java b/telephony/common/com/google/android/mms/util/PduCache.java index 94e38946f632..c380d6b3e30f 100644 --- a/telephony/common/com/google/android/mms/util/PduCache.java +++ b/telephony/common/com/google/android/mms/util/PduCache.java @@ -17,14 +17,13 @@ package com.google.android.mms.util; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentUris; import android.content.UriMatcher; import android.net.Uri; import android.provider.Telephony.Mms; import android.util.Log; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.util.HashMap; import java.util.HashSet; diff --git a/telephony/common/com/google/android/mms/util/PduCacheEntry.java b/telephony/common/com/google/android/mms/util/PduCacheEntry.java index 1ecd1bf93e7f..a4a25d2471ff 100644 --- a/telephony/common/com/google/android/mms/util/PduCacheEntry.java +++ b/telephony/common/com/google/android/mms/util/PduCacheEntry.java @@ -17,7 +17,7 @@ package com.google.android.mms.util; -import dalvik.annotation.compat.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import com.google.android.mms.pdu.GenericPdu; diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java index 2dd1dc11c2a9..31fe4d7683d6 100644 --- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java +++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java @@ -18,6 +18,7 @@ package com.google.android.mms.util; import android.app.ActivityManager; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -27,8 +28,6 @@ import android.net.Uri; import android.util.Log; import android.widget.Toast; -import dalvik.annotation.compat.UnsupportedAppUsage; - public final class SqliteWrapper { private static final String TAG = "SqliteWrapper"; private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE -- cgit v1.2.3 From 09014f10482fa6300e80a4bbb51d7f404129bc06 Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Fri, 24 Jan 2020 08:15:38 +0800 Subject: [Telephony Mainline] Remove hidden API usages in CarrierAppUtils Bug: 146904426 Test: Build Change-Id: Ib307e565b85e7d9a142efc7e3348b823dc46643d --- .../internal/telephony/CarrierAppUtils.java | 203 ++++++++++----------- 1 file changed, 98 insertions(+), 105 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 9bc534c2877a..c9a9d76c082e 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -20,12 +20,10 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.os.RemoteException; import android.os.UserHandle; -import android.permission.IPermissionManager; +import android.permission.PermissionManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.ArrayMap; @@ -77,7 +75,6 @@ public final class CarrierAppUtils { * privileged apps may have changed. */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { Log.d(TAG, "disableCarrierAppsUntilPrivileged"); @@ -88,14 +85,14 @@ public final class CarrierAppUtils { ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); ContentResolver contentResolver = getContentResolverForUser(context, userId); - disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, - telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, - systemCarrierAssociatedAppsDisabledUntilUsed); + disableCarrierAppsUntilPrivileged(callingPackage, telephonyManager, contentResolver, + userId, systemCarrierAppsDisabledUntilUsed, + systemCarrierAssociatedAppsDisabledUntilUsed, context); } /** - * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager, - * ContentResolver, int)}, but assumes that no carrier apps have carrier privileges. + * Like {@link #disableCarrierAppsUntilPrivileged(String, TelephonyManager, int, Context)}, + * but assumes that no carrier apps have carrier privileges. * * This prevents a potential race condition on first boot - since the app's default state is * enabled, we will initially disable it when the telephony stack is first initialized as it has @@ -105,8 +102,7 @@ public final class CarrierAppUtils { * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, int userId, - Context context) { + int userId, Context context) { if (DEBUG) { Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -114,13 +110,12 @@ public final class CarrierAppUtils { ArraySet systemCarrierAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierApps(); - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); ContentResolver contentResolver = getContentResolverForUser(context, userId); - disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, - null /* telephonyManager */, contentResolver, userId, - systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); + disableCarrierAppsUntilPrivileged(callingPackage, null /* telephonyManager */, + contentResolver, userId, systemCarrierAppsDisabledUntilUsed, + systemCarrierAssociatedAppsDisabledUntilUsed, context); } private static ContentResolver getContentResolverForUser(Context context, int userId) { @@ -136,21 +131,21 @@ public final class CarrierAppUtils { // Must be public b/c framework unit tests can't access package-private methods. @VisibleForTesting public static void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, - @Nullable TelephonyManager telephonyManager, - ContentResolver contentResolver, int userId, - ArraySet systemCarrierAppsDisabledUntilUsed, - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { + @Nullable TelephonyManager telephonyManager, ContentResolver contentResolver, + int userId, ArraySet systemCarrierAppsDisabledUntilUsed, + ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed, + Context context) { + PackageManager packageManager = context.getPackageManager(); + PermissionManager permissionManager = + (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); List candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( - packageManager, userId, systemCarrierAppsDisabledUntilUsed); + userId, systemCarrierAppsDisabledUntilUsed, context); if (candidates == null || candidates.isEmpty()) { return; } Map> associatedApps = getDefaultCarrierAssociatedAppsHelper( - packageManager, - userId, - systemCarrierAssociatedAppsDisabledUntilUsed); + userId, systemCarrierAssociatedAppsDisabledUntilUsed, context); List enabledCarrierPackages = new ArrayList<>(); boolean hasRunOnce = Settings.Secure.getInt(contentResolver, @@ -167,19 +162,18 @@ public final class CarrierAppUtils { && !ArrayUtils.contains(restrictedCarrierApps, packageName); // add hiddenUntilInstalled flag for carrier apps and associated apps - packageManager.setSystemAppHiddenUntilInstalled(packageName, true); + packageManager.setSystemAppState( + packageName, PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN); List associatedAppList = associatedApps.get(packageName); if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - packageManager.setSystemAppHiddenUntilInstalled( - associatedApp.packageName, - true - ); + packageManager.setSystemAppState(associatedApp.packageName, + PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN); } } - int enabledSetting = packageManager.getApplicationEnabledSetting(packageName, - userId); + int enabledSetting = context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager().getApplicationEnabledSetting(packageName); if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. @@ -190,24 +184,25 @@ public final class CarrierAppUtils { || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId); - packageManager.setSystemAppInstallState( - packageName, - true /*installed*/, - userId); - packageManager.setApplicationEnabledSetting( - packageName, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP, - userId, - callingPackage); + context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .setSystemAppState( + packageName, PackageManager.SYSTEM_APP_STATE_INSTALLED); + context.createPackageContextAsUser(callingPackage, 0, UserHandle.of(userId)) + .getPackageManager() + .setApplicationEnabledSetting( + packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); } // Also enable any associated apps for this carrier app. if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - int associatedAppEnabledSetting = - packageManager.getApplicationEnabledSetting( - associatedApp.packageName, userId); + int associatedAppEnabledSetting = context + .createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getApplicationEnabledSetting(associatedApp.packageName); if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || associatedAppEnabledSetting @@ -216,16 +211,17 @@ public final class CarrierAppUtils { & ApplicationInfo.FLAG_INSTALLED) == 0) { Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): ENABLED for user " + userId); - packageManager.setSystemAppInstallState( - associatedApp.packageName, - true /*installed*/, - userId); - packageManager.setApplicationEnabledSetting( - associatedApp.packageName, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP, - userId, - callingPackage); + context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .setSystemAppState(associatedApp.packageName, + PackageManager.SYSTEM_APP_STATE_INSTALLED); + context.createPackageContextAsUser( + callingPackage, 0, UserHandle.of(userId)) + .getPackageManager() + .setApplicationEnabledSetting( + associatedApp.packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); } } } @@ -240,10 +236,10 @@ public final class CarrierAppUtils { && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED for user " + userId); - packageManager.setSystemAppInstallState( - packageName, - false /*installed*/, - userId); + context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .setSystemAppState( + packageName, PackageManager.SYSTEM_APP_STATE_UNINSTALLED); } // Also disable any associated apps for this carrier app if this is the first @@ -252,9 +248,10 @@ public final class CarrierAppUtils { if (!hasRunOnce) { if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - int associatedAppEnabledSetting = - packageManager.getApplicationEnabledSetting( - associatedApp.packageName, userId); + int associatedAppEnabledSetting = context + .createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getApplicationEnabledSetting(associatedApp.packageName); if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (associatedApp.flags @@ -262,10 +259,10 @@ public final class CarrierAppUtils { Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): DISABLED_UNTIL_USED for user " + userId); - packageManager.setSystemAppInstallState( - associatedApp.packageName, - false /*installed*/, - userId); + context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .setSystemAppState(associatedApp.packageName, + PackageManager.SYSTEM_APP_STATE_UNINSTALLED); } } } @@ -283,9 +280,10 @@ public final class CarrierAppUtils { // apps. String[] packageNames = new String[enabledCarrierPackages.size()]; enabledCarrierPackages.toArray(packageNames); - permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); + permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, + UserHandle.of(userId), Runnable::run, isSuccess -> { }); } - } catch (RemoteException e) { + } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Could not reach PackageManager", e); } } @@ -294,13 +292,13 @@ public final class CarrierAppUtils { * Returns the list of "default" carrier apps. * * This is the subset of apps returned by - * {@link #getDefaultCarrierAppCandidates(IPackageManager, int)} which currently have carrier + * {@link #getDefaultCarrierAppCandidates(int, Context)} which currently have carrier * privileges per the SIM(s) inserted in the device. */ - public static List getDefaultCarrierApps(IPackageManager packageManager, - TelephonyManager telephonyManager, int userId) { + public static List getDefaultCarrierApps( + TelephonyManager telephonyManager, int userId, Context context) { // Get all system apps from the default list. - List candidates = getDefaultCarrierAppCandidates(packageManager, userId); + List candidates = getDefaultCarrierAppCandidates(userId, context); if (candidates == null || candidates.isEmpty()) { return null; } @@ -326,25 +324,23 @@ public final class CarrierAppUtils { * Returns the list of "default" carrier app candidates. * * These are the apps subject to the hiding/showing logic in - * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, IPackageManager, - * TelephonyManager, ContentResolver, int)}, as well as the apps which should have default + * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, TelephonyManager, int, + * Context)}, as well as the apps which should have default * permissions granted, when a matching SIM is inserted. * * Whether or not the app is actually considered a default app depends on whether the app has * carrier privileges as determined by the SIMs in the device. */ public static List getDefaultCarrierAppCandidates( - IPackageManager packageManager, int userId) { + int userId, Context context) { ArraySet systemCarrierAppsDisabledUntilUsed = SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps(); - return getDefaultCarrierAppCandidatesHelper(packageManager, userId, - systemCarrierAppsDisabledUntilUsed); + return getDefaultCarrierAppCandidatesHelper(userId, systemCarrierAppsDisabledUntilUsed, + context); } private static List getDefaultCarrierAppCandidatesHelper( - IPackageManager packageManager, - int userId, - ArraySet systemCarrierAppsDisabledUntilUsed) { + int userId, ArraySet systemCarrierAppsDisabledUntilUsed, Context context) { if (systemCarrierAppsDisabledUntilUsed == null) { return null; } @@ -358,7 +354,7 @@ public final class CarrierAppUtils { for (int i = 0; i < size; i++) { String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); ApplicationInfo ai = - getApplicationInfoIfSystemApp(packageManager, userId, packageName); + getApplicationInfoIfSystemApp(userId, packageName, context); if (ai != null) { apps.add(ai); } @@ -367,9 +363,7 @@ public final class CarrierAppUtils { } private static List getDefaultNotUpdatedCarrierAppCandidatesHelper( - IPackageManager packageManager, - int userId, - ArraySet systemCarrierAppsDisabledUntilUsed) { + int userId, ArraySet systemCarrierAppsDisabledUntilUsed, Context context) { if (systemCarrierAppsDisabledUntilUsed == null) { return null; } @@ -383,7 +377,7 @@ public final class CarrierAppUtils { for (int i = 0; i < size; i++) { String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName); + getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context); if (ai != null) { apps.add(ai); } @@ -392,9 +386,8 @@ public final class CarrierAppUtils { } private static Map> getDefaultCarrierAssociatedAppsHelper( - IPackageManager packageManager, - int userId, - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed) { + int userId, ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed, + Context context) { int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); Map> associatedApps = new ArrayMap<>(size); for (int i = 0; i < size; i++) { @@ -404,7 +397,7 @@ public final class CarrierAppUtils { for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = getApplicationInfoIfNotUpdatedSystemApp( - packageManager, userId, associatedAppPackages.get(j)); + userId, associatedAppPackages.get(j), context); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. if (ai != null) { @@ -422,19 +415,19 @@ public final class CarrierAppUtils { @Nullable private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( - IPackageManager packageManager, - int userId, - String packageName) { + int userId, String packageName, Context context) { try { - ApplicationInfo ai = packageManager.getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_FACTORY_ONLY, userId); + ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_FACTORY_ONLY); if (ai != null) { return ai; } - } catch (RemoteException e) { + } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Could not reach PackageManager", e); } return null; @@ -442,18 +435,18 @@ public final class CarrierAppUtils { @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( - IPackageManager packageManager, - int userId, - String packageName) { + int userId, String packageName, Context context) { try { - ApplicationInfo ai = packageManager.getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY, userId); + ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY); if (ai != null) { return ai; } - } catch (RemoteException e) { + } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Could not reach PackageManager", e); } return null; -- cgit v1.2.3 From 6b534e4107fdc91f908994f253b38c899b96cab2 Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Thu, 23 Jan 2020 18:51:47 -0800 Subject: Remove usage of MetricsLogger in SmsApplication. Bug: 140908357 Test: build Change-Id: I285de118d291fc3112518419fb92544f0ba942d2 --- .../common/com/android/internal/telephony/SmsApplication.java | 7 ------- 1 file changed, 7 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 32f9d53e59f8..a71accf8148e 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -45,8 +45,6 @@ import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.Collection; import java.util.HashMap; @@ -726,11 +724,6 @@ public final class SmsApplication { new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); context.sendBroadcastAsUser(intent, userHandle, permission.MONITOR_DEFAULT_SMS_PACKAGE); - - if (applicationData != null) { - MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED, - applicationData.mPackageName); - } } /** -- cgit v1.2.3 From ed8bca0bf81d4cff947f8013991492514e82d55f Mon Sep 17 00:00:00 2001 From: Muhammad Qureshi Date: Fri, 24 Jan 2020 16:07:49 -0800 Subject: Add TelephonyCommonStatsLog Add TelephonyCommonStatsLog to framework-telephony-common-sources to replace StatsLog call in TelephonyPermissions. This allows TelephonyPermissions to be part of frameworks.jar after being jarjar'ed and also be included statically in Mainline modules. Bug: 145952197 Test: m frameworks-minus-apex Test: fastboot flashall Test: atest TelephonyCommonTests Change-Id: I0b9e946e2b4a64a05acaf3132dffd8fc22f7d51d --- .../common/com/android/internal/telephony/TelephonyPermissions.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 89cd4615637b..7b3aace798f7 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -31,7 +31,6 @@ import android.os.UserHandle; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; -import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; @@ -393,8 +392,8 @@ public final class TelephonyPermissions { invokedMethods = sReportedDeviceIDPackages.get(callingPackage); } invokedMethods.add(message); - StatsLog.write(StatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, callingPackage, message, - isPreinstalled, false); + TelephonyCommonStatsLog.write(TelephonyCommonStatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, + callingPackage, message, isPreinstalled, false); } Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + ":isPreinstalled=" + isPreinstalled); -- cgit v1.2.3 From 113d4543ad3c758772a616cc17317a479cd99c19 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Thu, 23 Jan 2020 17:38:28 -0800 Subject: Remove the usage of Downloads.* constants. Replaced them with local constants as it is only for internal use. Test: basic sanity Bug: 140908357 Change-Id: Icebabc4d16c2e6d5b6e2fb311fee697e8acfa285 --- .../google/android/mms/util/DrmConvertSession.java | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/google/android/mms/util/DrmConvertSession.java b/telephony/common/com/google/android/mms/util/DrmConvertSession.java index 156c7ad8baac..17ab15470670 100644 --- a/telephony/common/com/google/android/mms/util/DrmConvertSession.java +++ b/telephony/common/com/google/android/mms/util/DrmConvertSession.java @@ -20,7 +20,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.drm.DrmConvertedStatus; import android.drm.DrmManagerClient; -import android.provider.Downloads; import android.util.Log; import java.io.FileNotFoundException; @@ -33,6 +32,13 @@ public class DrmConvertSession { private int mConvertSessionId; private static final String TAG = "DrmConvertSession"; + // These values are copied from Downloads.Impl.* for backward compatibility since + // {@link #close()} that uses it is marked @UnsupportedAppUsage. + public static final int STATUS_SUCCESS = 200; + public static final int STATUS_NOT_ACCEPTABLE = 406; + public static final int STATUS_UNKNOWN_ERROR = 491; + public static final int STATUS_FILE_ERROR = 492; + private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) { mDrmClient = drmClient; mConvertSessionId = convertSessionId; @@ -118,38 +124,38 @@ public class DrmConvertSession { * Ends a conversion session of a file. * * @param fileName The filename of the converted file. - * @return Downloads.Impl.STATUS_SUCCESS if execution is ok. - * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not - * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem + * @return STATUS_SUCCESS if execution is ok. + * STATUS_FILE_ERROR in case converted file can not + * be accessed. STATUS_NOT_ACCEPTABLE if a problem * occurs when accessing drm framework. - * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred. + * STATUS_UNKNOWN_ERROR if a general error occurred. */ @UnsupportedAppUsage public int close(String filename) { DrmConvertedStatus convertedStatus = null; - int result = Downloads.Impl.STATUS_UNKNOWN_ERROR; + int result = STATUS_UNKNOWN_ERROR; if (mDrmClient != null && mConvertSessionId >= 0) { try { convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId); if (convertedStatus == null || convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK || convertedStatus.convertedData == null) { - result = Downloads.Impl.STATUS_NOT_ACCEPTABLE; + result = STATUS_NOT_ACCEPTABLE; } else { RandomAccessFile rndAccessFile = null; try { rndAccessFile = new RandomAccessFile(filename, "rw"); rndAccessFile.seek(convertedStatus.offset); rndAccessFile.write(convertedStatus.convertedData); - result = Downloads.Impl.STATUS_SUCCESS; + result = STATUS_SUCCESS; } catch (FileNotFoundException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "File: " + filename + " could not be found.", e); } catch (IOException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "Could not access File: " + filename + " .", e); } catch (IllegalArgumentException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "Could not open file in mode: rw", e); } catch (SecurityException e) { Log.w(TAG, "Access to File: " + filename + @@ -159,7 +165,7 @@ public class DrmConvertSession { try { rndAccessFile.close(); } catch (IOException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "Failed to close File:" + filename + ".", e); } -- cgit v1.2.3 From e707a1064dd37d9bbbdb78951a1f5b87a06d38c6 Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Mon, 27 Jan 2020 17:25:38 -0800 Subject: Remove hidden API usage Bug: 137202333 Test: make Change-Id: I4448d8f458924aa9eddb631c3c4d4ea21a02e165 --- telephony/common/com/android/internal/telephony/SmsApplication.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 32f9d53e59f8..b2319000af98 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -35,7 +35,6 @@ import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; -import android.os.Debug; import android.os.Process; import android.os.UserHandle; import android.provider.Telephony; @@ -197,7 +196,7 @@ public final class SmsApplication { final int callingUid = Binder.getCallingUid(); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid=" - + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4)); + + android.os.Process.myUid()); } if (UserHandle.getAppId(callingUid) < android.os.Process.FIRST_APPLICATION_UID) { -- cgit v1.2.3 From f77ad42dc6c39723734638308ca191dbbded1fac Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Mon, 27 Jan 2020 17:53:16 -0800 Subject: Telephony: do not use hidden API Bug: 137202333 Test: make Change-Id: Id0177c9c82d7831d165650c96a3cdea0566a6e2f --- .../android/internal/telephony/SmsApplication.java | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index a71accf8148e..ba97a7d049c0 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -666,10 +666,22 @@ public final class SmsApplication { } } + /** + * Broadcast action: + * Same as {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} but it's implicit (e.g. sent to + * all apps) and requires + * {@link #PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE} to receive. + */ + public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL = + "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL"; + + public static final String PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE = + "android.permission.MONITOR_DEFAULT_SMS_PACKAGE"; + /** * Sends broadcasts on sms app change: * {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} - * {@link Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL} + * {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL} */ public static void broadcastSmsAppChange(Context context, UserHandle userHandle, @Nullable String oldPackage, @Nullable String newPackage) { @@ -719,11 +731,11 @@ public final class SmsApplication { } // Send an implicit broadcast for the system server. - // (or anyone with MONITOR_DEFAULT_SMS_PACKAGE, really.) + // (or anyone with PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE, really.) final Intent intent = - new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); + new Intent(ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); context.sendBroadcastAsUser(intent, userHandle, - permission.MONITOR_DEFAULT_SMS_PACKAGE); + PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE); } /** -- cgit v1.2.3 From 55e9590016ba9d9b7f62a8dfcdf3da5893af7c89 Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Mon, 27 Jan 2020 15:26:00 -0800 Subject: Remoe usage of CollectionUtils as it's not exposed to mainline modules. Bug: 140908357 Test: build Change-Id: I5f56747be8bc4702e58b7fe27c21f7959bdd76de --- .../common/com/android/internal/telephony/util/TelephonyUtils.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index a7ad884ca107..682697469af9 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -28,6 +28,8 @@ import android.os.RemoteException; import android.os.SystemProperties; import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -61,6 +63,11 @@ public final class TelephonyUtils { return str == null ? "" : str; } + /** Returns an empty list if the input is {@code null}. */ + public static @NonNull List emptyIfNull(@Nullable List cur) { + return cur == null ? Collections.emptyList() : cur; + } + /** Throws a {@link RuntimeException} that wrapps the {@link RemoteException}. */ public static RuntimeException rethrowAsRuntimeException(RemoteException remoteException) { throw new RuntimeException(remoteException); -- cgit v1.2.3 From b30b3ba2323fdaeb8ce57f4701cdcd0cdfef7557 Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Tue, 28 Jan 2020 11:46:34 -0800 Subject: Remove usage of android.os.Build.IS_USER Bug: 140908357 Test: build Change-Id: I572736ff773e71e420a732aa4c56d5dceee1a454 --- telephony/common/com/android/internal/telephony/SmsNumberUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index cd365a113189..95098e89539d 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -31,6 +31,7 @@ import android.util.Log; import com.android.internal.telephony.HbpcdLookup.MccIdd; import com.android.internal.telephony.HbpcdLookup.MccLookup; +import com.android.internal.telephony.util.TelephonyUtils; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -677,7 +678,7 @@ public class SmsNumberUtils { */ private static String secureHash(byte[] input) { // Refrain from logging user personal information in user build. - if (android.os.Build.IS_USER) { + if (TelephonyUtils.IS_USER) { return "****"; } -- cgit v1.2.3 From e8953ae762dcc3c5b75ca37c0d050f7382b37946 Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Wed, 29 Jan 2020 10:38:58 -0800 Subject: DeviceIdentifierAccessDenied: deprecate is_preinstalled For statsd log DeviceIdentifierAccessDenied, field is_preinstalled is provided by a hidden API. TelephonyPermissions cannot access hidden API as a mainline module. So deprecate the field and always false. Bug: 137202333 Test: make Change-Id: Idb9b91015a867db9b9b0d2d8c1282e2afea65132 --- .../com/android/internal/telephony/TelephonyPermissions.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 7b3aace798f7..b89772538c6d 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -363,16 +363,10 @@ public final class TelephonyPermissions { */ private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid, int uid, String callingPackage, String message) { - boolean isPreinstalled = false; ApplicationInfo callingPackageInfo = null; try { callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser( callingPackage, 0, UserHandle.getUserHandleForUid(uid)); - if (callingPackageInfo != null) { - if (callingPackageInfo.isSystemApp()) { - isPreinstalled = true; - } - } } catch (PackageManager.NameNotFoundException e) { // If the application info for the calling package could not be found then assume the // calling app is a non-preinstalled app to detect any issues with the check @@ -393,10 +387,9 @@ public final class TelephonyPermissions { } invokedMethods.add(message); TelephonyCommonStatsLog.write(TelephonyCommonStatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, - callingPackage, message, isPreinstalled, false); + callingPackage, message, /* isPreinstalled= */ false, false); } - Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message - + ":isPreinstalled=" + isPreinstalled); + Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message); // if the target SDK is pre-Q then check if the calling package would have previously // had access to device identifiers. if (callingPackageInfo != null && ( -- cgit v1.2.3 From af41d87edeaddca5c256d1edf14eb1b7e58ed929 Mon Sep 17 00:00:00 2001 From: Sooraj Sasindran Date: Wed, 5 Feb 2020 17:17:26 -0800 Subject: Use FLAG_UPDATED_SYSTEM_APP Use FLAG_UPDATED_SYSTEM_APP find if an app is updated system app instead of MATCH_FACTORY_ONLY Bug: 148308979 Test: Unit test for CarrierAppUtils and system test to verify fi is not getting uninstalled Change-Id: I1991e98bb1edb7d0628a7011df7488fbf5de3579 --- .../internal/telephony/CarrierAppUtils.java | 61 +++++----------------- 1 file changed, 13 insertions(+), 48 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 553bcff931d2..e97cfaf0afa6 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -123,6 +123,14 @@ public final class CarrierAppUtils { return userContext.getContentResolver(); } + private static boolean isUpdatedSystemApp(ApplicationInfo ai) { + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } + + return false; + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. @@ -137,7 +145,7 @@ public final class CarrierAppUtils { PackageManager packageManager = context.getPackageManager(); PermissionManager permissionManager = (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); - List candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + List candidates = getDefaultCarrierAppCandidatesHelper( userId, systemCarrierAppsDisabledUntilUsed, context); if (candidates == null || candidates.isEmpty()) { return; @@ -176,7 +184,7 @@ public final class CarrierAppUtils { if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED @@ -230,7 +238,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -361,29 +369,6 @@ public final class CarrierAppUtils { return apps; } - private static List getDefaultNotUpdatedCarrierAppCandidatesHelper( - int userId, ArraySet systemCarrierAppsDisabledUntilUsed, Context context) { - if (systemCarrierAppsDisabledUntilUsed == null) { - return null; - } - - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); - ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context); - if (ai != null) { - apps.add(ai); - } - } - return apps; - } - private static Map> getDefaultCarrierAssociatedAppsHelper( int userId, ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { @@ -395,11 +380,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp( + getApplicationInfoIfSystemApp( userId, associatedAppPackages.get(j), context); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null) { + if (ai != null && !isUpdatedSystemApp(ai)) { List appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -412,26 +397,6 @@ public final class CarrierAppUtils { return associatedApps; } - @Nullable - private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( - int userId, String packageName, Context context) { - try { - ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_FACTORY_ONLY); - if (ai != null) { - return ai; - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Could not reach PackageManager", e); - } - return null; - } - @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( int userId, String packageName, Context context) { -- cgit v1.2.3 From fb2497869930633c064eaac29aa3b965222c5ea6 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Thu, 27 Feb 2020 15:23:32 -0800 Subject: Migrate CarrierAppUtils to use SystemConfigManager Use the new SystemConfigManager APIs in CarrierAppUtils instead of accessing SystemConfig directly. Fixes: 143112379 Test: manual Change-Id: I04c61400f6f703e353ffdafb0f86df93ba3494dc --- .../internal/telephony/CarrierAppUtils.java | 64 ++++++++++------------ 1 file changed, 30 insertions(+), 34 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index e97cfaf0afa6..d9ae48f6b833 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -22,21 +22,21 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.os.SystemConfigManager; import android.os.UserHandle; import android.permission.PermissionManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.util.ArrayUtils; -import com.android.server.SystemConfig; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * Utilities for handling carrier applications. @@ -53,19 +53,19 @@ public final class CarrierAppUtils { * Handle preinstalled carrier apps which should be disabled until a matching SIM is inserted. * * Evaluates the list of applications in - * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierApps()}. We want to disable each - * such application which is present on the system image until the user inserts a SIM which - * causes that application to gain carrier privilege (indicating a "match"), without interfering - * with the user if they opt to enable/disable the app explicitly. + * {@link SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierApps()}. We want to disable + * each such application which is present on the system image until the user inserts a SIM + * which causes that application to gain carrier privilege (indicating a "match"), without + * interfering with the user if they opt to enable/disable the app explicitly. * * So, for each such app, we either disable until used IFF the app is not carrier privileged AND * in the default state (e.g. not explicitly DISABLED/DISABLED_BY_USER/ENABLED), or we enable if * the app is carrier privileged and in either the default state or DISABLED_UNTIL_USED. * * In addition, there is a list of carrier-associated applications in - * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app in this - * list is associated with a carrier app. When the given carrier app is enabled/disabled per the - * above, the associated applications are enabled/disabled to match. + * {@link SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app + * in this list is associated with a carrier app. When the given carrier app is enabled/disabled + * per the above, the associated applications are enabled/disabled to match. * * When enabling a carrier app we also grant it default permissions. * @@ -78,10 +78,10 @@ public final class CarrierAppUtils { if (DEBUG) { Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } - SystemConfig config = SystemConfig.getInstance(); - ArraySet systemCarrierAppsDisabledUntilUsed = + SystemConfigManager config = context.getSystemService(SystemConfigManager.class); + Set systemCarrierAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierApps(); - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = + Map> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, telephonyManager, contentResolver, @@ -105,11 +105,11 @@ public final class CarrierAppUtils { if (DEBUG) { Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } - SystemConfig config = SystemConfig.getInstance(); - ArraySet systemCarrierAppsDisabledUntilUsed = + SystemConfigManager config = context.getSystemService(SystemConfigManager.class); + Set systemCarrierAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierApps(); - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed = + Map> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, null /* telephonyManager */, @@ -139,8 +139,8 @@ public final class CarrierAppUtils { @VisibleForTesting public static void disableCarrierAppsUntilPrivileged(String callingPackage, @Nullable TelephonyManager telephonyManager, ContentResolver contentResolver, - int userId, ArraySet systemCarrierAppsDisabledUntilUsed, - ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed, + int userId, Set systemCarrierAppsDisabledUntilUsed, + Map> systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { PackageManager packageManager = context.getPackageManager(); PermissionManager permissionManager = @@ -340,26 +340,22 @@ public final class CarrierAppUtils { */ public static List getDefaultCarrierAppCandidates( int userId, Context context) { - ArraySet systemCarrierAppsDisabledUntilUsed = - SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps(); + Set systemCarrierAppsDisabledUntilUsed = + context.getSystemService(SystemConfigManager.class) + .getDisabledUntilUsedPreinstalledCarrierApps(); return getDefaultCarrierAppCandidatesHelper(userId, systemCarrierAppsDisabledUntilUsed, context); } private static List getDefaultCarrierAppCandidatesHelper( - int userId, ArraySet systemCarrierAppsDisabledUntilUsed, Context context) { - if (systemCarrierAppsDisabledUntilUsed == null) { + int userId, Set systemCarrierAppsDisabledUntilUsed, Context context) { + if (systemCarrierAppsDisabledUntilUsed == null + || systemCarrierAppsDisabledUntilUsed.isEmpty()) { return null; } - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + List apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.size()); + for (String packageName : systemCarrierAppsDisabledUntilUsed) { ApplicationInfo ai = getApplicationInfoIfSystemApp(userId, packageName, context); if (ai != null) { @@ -370,14 +366,14 @@ public final class CarrierAppUtils { } private static Map> getDefaultCarrierAssociatedAppsHelper( - int userId, ArrayMap> systemCarrierAssociatedAppsDisabledUntilUsed, + int userId, Map> systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); Map> associatedApps = new ArrayMap<>(size); - for (int i = 0; i < size; i++) { - String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i); - List associatedAppPackages = - systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); + for (Map.Entry> entry + : systemCarrierAssociatedAppsDisabledUntilUsed.entrySet()) { + String carrierAppPackage = entry.getKey(); + List associatedAppPackages = entry.getValue(); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = getApplicationInfoIfSystemApp( -- cgit v1.2.3 From 05e97205523d6ca0f941b0707539f9be6e54ba54 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Thu, 20 Feb 2020 13:46:51 -0800 Subject: Fix permission check for get/setSmscAddress. Based on api-council feedback. Incorporated other suggestions too. Test: basic SMS sanity Bug: 149236716 Merged-in: I60b300d1b2d4e8b67cfc121e10a7957a8f0aaac8 Change-Id: I60b300d1b2d4e8b67cfc121e10a7957a8f0aaac8 (cherry picked from commit 15a3b86e897fa696fce75362d0cab64fea710f31) --- .../com/android/internal/telephony/SmsApplication.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index bb6f154335a9..d54c054e2f82 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -1057,7 +1057,8 @@ public final class SmsApplication { } /** - * Check if a package is default sms app (or equivalent, like bluetooth) + * Check if a package is default sms app (or equivalent, like bluetooth), and verify that + * packageName belongs to the caller. * * @param context context from the calling app * @param packageName the name of the package to be checked @@ -1066,8 +1067,22 @@ public final class SmsApplication { @UnsupportedAppUsage public static boolean isDefaultSmsApplication(Context context, String packageName) { if (packageName == null) { + Log.e(LOG_TAG, "isDefaultSmsApplication: packageName is null"); return false; } + try { + if (Binder.getCallingUid() + == context.getPackageManager().getPackageUid(packageName, 0)) { + Log.e(LOG_TAG, "isDefaultSmsApplication: " + packageName + " calling uid " + + context.getPackageManager().getPackageUid(packageName, 0) + + " does not match calling uid " + Binder.getCallingUid()); + return false; + } + } catch (NameNotFoundException ex) { + Log.e(LOG_TAG, "isDefaultSmsApplication: packageName " + packageName + " not found"); + return false; + } + final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context); if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName)) || BLUETOOTH_PACKAGE_NAME.equals(packageName)) { -- cgit v1.2.3 From dec964ceb6eba0de5fa999075ddafdcbc0a41392 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Tue, 3 Mar 2020 19:18:23 +0000 Subject: Revert "Fix permission check for get/setSmscAddress." This reverts commit 05e97205523d6ca0f941b0707539f9be6e54ba54. Reason for revert: b/150666039 Bug: 150666039 Change-Id: I67a4ffe94e3caabd04a5b9305de3e476867bfbd1 --- .../com/android/internal/telephony/SmsApplication.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index d54c054e2f82..bb6f154335a9 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -1057,8 +1057,7 @@ public final class SmsApplication { } /** - * Check if a package is default sms app (or equivalent, like bluetooth), and verify that - * packageName belongs to the caller. + * Check if a package is default sms app (or equivalent, like bluetooth) * * @param context context from the calling app * @param packageName the name of the package to be checked @@ -1067,22 +1066,8 @@ public final class SmsApplication { @UnsupportedAppUsage public static boolean isDefaultSmsApplication(Context context, String packageName) { if (packageName == null) { - Log.e(LOG_TAG, "isDefaultSmsApplication: packageName is null"); return false; } - try { - if (Binder.getCallingUid() - == context.getPackageManager().getPackageUid(packageName, 0)) { - Log.e(LOG_TAG, "isDefaultSmsApplication: " + packageName + " calling uid " - + context.getPackageManager().getPackageUid(packageName, 0) - + " does not match calling uid " + Binder.getCallingUid()); - return false; - } - } catch (NameNotFoundException ex) { - Log.e(LOG_TAG, "isDefaultSmsApplication: packageName " + packageName + " not found"); - return false; - } - final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context); if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName)) || BLUETOOTH_PACKAGE_NAME.equals(packageName)) { -- cgit v1.2.3 From d238b8a75933bca6cec39f5c34c2e6735b25a7d8 Mon Sep 17 00:00:00 2001 From: Sarah Chin Date: Thu, 12 Mar 2020 22:39:31 +0000 Subject: Revert "Expose hidden resources used in telephony/common" This reverts commit a63e69cf94e5c6ea9de5eb312d36c96818711a2d. Reason for revert: Remove mainline API for R Test: build Bug: 148174114 Change-Id: I712958984254ca6f16a9604d2aab532500dc1ca9 --- .../common/com/android/internal/telephony/CarrierAppUtils.java | 3 ++- .../common/com/android/internal/telephony/GsmAlphabet.java | 10 +++++----- .../common/com/google/android/mms/util/SqliteWrapper.java | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index d9ae48f6b833..b3d7c0d36763 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -30,6 +30,7 @@ import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.util.ArrayUtils; @@ -162,7 +163,7 @@ public final class CarrierAppUtils { for (ApplicationInfo ai : candidates) { String packageName = ai.packageName; String[] restrictedCarrierApps = Resources.getSystem().getStringArray( - android.R.array.config_restrictedPreinstalledCarrierApps); + R.array.config_restrictedPreinstalledCarrierApps); boolean hasPrivileges = telephonyManager != null && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index c62cec270440..5c53f7e5a4d0 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,10 +19,12 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import android.text.TextUtils; import android.util.Log; +import android.text.TextUtils; import android.util.SparseIntArray; +import com.android.internal.R; + import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; @@ -1087,10 +1089,8 @@ public class GsmAlphabet { private static void enableCountrySpecificEncodings() { Resources r = Resources.getSystem(); // See comments in frameworks/base/core/res/res/values/config.xml for allowed values - sEnabledSingleShiftTables = r.getIntArray( - android.R.array.config_sms_enabled_single_shift_tables); - sEnabledLockingShiftTables = r.getIntArray( - android.R.array.config_sms_enabled_locking_shift_tables); + sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables); + sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables); if (sEnabledSingleShiftTables.length > 0) { sHighestEnabledSingleShiftCode = diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java index 4871434ebc31..31fe4d7683d6 100644 --- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java +++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java @@ -60,7 +60,8 @@ public final class SqliteWrapper { @UnsupportedAppUsage public static void checkSQLiteException(Context context, SQLiteException e) { if (isLowMemory(e)) { - Toast.makeText(context, android.R.string.low_memory, Toast.LENGTH_SHORT).show(); + Toast.makeText(context, com.android.internal.R.string.low_memory, + Toast.LENGTH_SHORT).show(); } else { throw e; } -- cgit v1.2.3 From 3dde71f506a35465bd8034794556df1ac300017e Mon Sep 17 00:00:00 2001 From: Robert Greenwalt Date: Tue, 7 Jan 2020 16:18:29 -0800 Subject: Move getMsisdn and getLine1Number to READ_PHONE_NUMBERS This is done to get dangerous stuff off the READ_PHONE_STATE permission. Also keeping READ_PHONE_STATE for apps targeting old SDK versions. Refactored getPhoneNumber to allow READ_PHONE_STATE pre-R Bug: 136160623 Test: atest SubInfoControllerTest, TelephonyPermissionsTest Change-Id: I5c0adcbe432ffcbb22ac8959792ee60da7039f15 --- .../internal/telephony/TelephonyPermissions.java | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index b89772538c6d..f0f9721744dd 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -437,8 +437,9 @@ public final class TelephonyPermissions { /** * Returns whether the caller can read phone numbers. * - *

Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the - * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers. + *

Besides apps with the ability to read phone state per {@link #checkReadPhoneState} + * (only prior to R), the default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS + * can also read phone numbers. */ public static boolean checkCallingOrSelfReadPhoneNumber( Context context, int subId, String callingPackage, @Nullable String callingFeatureId, @@ -451,8 +452,9 @@ public final class TelephonyPermissions { /** * Returns whether the caller can read phone numbers. * - *

Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the - * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers. + *

Besides apps with the ability to read phone state per {@link #checkReadPhoneState} + * (only prior to R), the default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS + * can also read phone numbers. */ @VisibleForTesting public static boolean checkReadPhoneNumber( @@ -468,12 +470,15 @@ public final class TelephonyPermissions { // NOTE(b/73308711): If an app has one of the following AppOps bits explicitly revoked, they // will be denied access, even if they have another permission and AppOps bit if needed. - // First, check if we can read the phone state. + // First, check if we can read the phone state and the SDK version is below R. try { - return checkReadPhoneState( - context, subId, pid, uid, callingPackage, callingFeatureId, - message); - } catch (SecurityException readPhoneStateSecurityException) { + ApplicationInfo info = context.getPackageManager().getApplicationInfoAsUser( + callingPackage, 0, UserHandle.getUserHandleForUid(Binder.getCallingUid())); + if (info.targetSdkVersion <= Build.VERSION_CODES.Q) { + return checkReadPhoneState( + context, subId, pid, uid, callingPackage, callingFeatureId, message); + } + } catch (SecurityException | PackageManager.NameNotFoundException e) { } // Can be read with READ_SMS too. try { -- cgit v1.2.3 From ceba2adbbdbdd40ae8a9d9e9784cf1cb484342e9 Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Mon, 16 Mar 2020 04:45:23 -0700 Subject: [Telephony Mainline] Rename getActiveAndHiddenSubscriptionIdList and guard with permission Bug: 150878235 Fix: 150878235 Test: Build Change-Id: I7f3b1f5170e032336d9b39b322c49e2b92573586 --- .../common/com/android/internal/telephony/TelephonyPermissions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index f0f9721744dd..68b17688c22e 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -616,7 +616,7 @@ public final class TelephonyPermissions { private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) { SubscriptionManager sm = (SubscriptionManager) context.getSystemService( Context.TELEPHONY_SUBSCRIPTION_SERVICE); - int[] activeSubIds = sm.getActiveAndHiddenSubscriptionIdList(); + int[] activeSubIds = sm.getCompleteActiveSubscriptionIdList(); for (int activeSubId : activeSubIds) { if (getCarrierPrivilegeStatus(context, activeSubId, uid) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { -- cgit v1.2.3 From 56a84b26f4b5f195ad5157fa99b46f72aab496a4 Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Wed, 4 Mar 2020 20:41:36 -0800 Subject: Refactor device ID access SystemAPI to PermissionManager Based on feedback during the API review of the new SystemAPI for telephony to check device identifier access the method was moved from DevicePolicyManager to a more generic location to perform the non-subscriber portions of the check. Bug: 147761267 Test: atest TelephonyPermissionsTest Test: atest PermissionManagerServiceTest Test: atest DeviceIdentifierTest Test: atest DeviceOwnerTest#testDeviceOwnerCanGetDeviceIdentifiers Test: atest TelephonyManagerTest Test: atest DeviceOwnerTest#testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission Test: atest ManagedProfileTest#testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testProfileOwnerCanGetDeviceIdentifiers Change-Id: Ic1867dad0b2369f2dc1a7d31facb65f89131376f --- .../internal/telephony/TelephonyPermissions.java | 38 +++------------------- 1 file changed, 5 insertions(+), 33 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 68b17688c22e..0b331744d922 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -20,7 +20,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -28,6 +27,7 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; +import android.permission.PermissionManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; @@ -303,14 +303,10 @@ public final class TelephonyPermissions { String message, boolean allowCarrierPrivilegeOnAnySub) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); - // Allow system and root access to the device identifiers. - final int appId = UserHandle.getAppId(uid); - if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) { - return true; - } - // Allow access to packages that have the READ_PRIVILEGED_PHONE_STATE permission. - if (context.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, - uid) == PackageManager.PERMISSION_GRANTED) { + PermissionManager permissionManager = (PermissionManager) context.getSystemService( + Context.PERMISSION_SERVICE); + if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId, + pid, uid) == PackageManager.PERMISSION_GRANTED) { return true; } @@ -323,30 +319,6 @@ public final class TelephonyPermissions { return true; } - // if the calling package is not null then perform the DevicePolicyManager device / - // profile owner and Appop checks. - if (callingPackage != null) { - // Allow access to an app that has been granted the READ_DEVICE_IDENTIFIERS app op. - long token = Binder.clearCallingIdentity(); - AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService( - Context.APP_OPS_SERVICE); - try { - if (appOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, uid, - callingPackage, callingFeatureId, null) == AppOpsManager.MODE_ALLOWED) { - return true; - } - } finally { - Binder.restoreCallingIdentity(token); - } - // Allow access to a device / profile owner app. - DevicePolicyManager devicePolicyManager = - (DevicePolicyManager) context.getSystemService( - Context.DEVICE_POLICY_SERVICE); - if (devicePolicyManager != null && devicePolicyManager.hasDeviceIdentifierAccess( - callingPackage, pid, uid)) { - return true; - } - } return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage, message); } -- cgit v1.2.3 From 48273a3520897cf16aaa17dc3b5b8a301612d71f Mon Sep 17 00:00:00 2001 From: Sarah Chin Date: Tue, 24 Mar 2020 22:32:36 -0700 Subject: Update READ_PHONE_NUMBER security checks For SDK R+, READ_PRIVILEGE_PHONE_STATE and carrier privilege should allow access. Test: atest TelephonyPermissionsTest Bug: 151952050 Change-Id: Ie97d0b195937d4729875afd1e74357c1284e101f --- .../internal/telephony/TelephonyPermissions.java | 30 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 0b331744d922..7e02966779a2 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -442,16 +442,40 @@ public final class TelephonyPermissions { // NOTE(b/73308711): If an app has one of the following AppOps bits explicitly revoked, they // will be denied access, even if they have another permission and AppOps bit if needed. - // First, check if we can read the phone state and the SDK version is below R. + // First, check if the SDK version is below R + boolean preR = false; try { ApplicationInfo info = context.getPackageManager().getApplicationInfoAsUser( callingPackage, 0, UserHandle.getUserHandleForUid(Binder.getCallingUid())); - if (info.targetSdkVersion <= Build.VERSION_CODES.Q) { + preR = info.targetSdkVersion <= Build.VERSION_CODES.Q; + } catch (PackageManager.NameNotFoundException nameNotFoundException) { + } + if (preR) { + // SDK < R allows READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or carrier privilege + try { return checkReadPhoneState( context, subId, pid, uid, callingPackage, callingFeatureId, message); + } catch (SecurityException readPhoneStateException) { + } + } else { + // SDK >= R allows READ_PRIVILEGED_PHONE_STATE or carrier privilege + try { + context.enforcePermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); + // Skip checking for runtime permission since caller has privileged permission + return true; + } catch (SecurityException readPrivilegedPhoneStateException) { + if (SubscriptionManager.isValidSubscriptionId(subId)) { + try { + enforceCarrierPrivilege(context, subId, uid, message); + // Skip checking for runtime permission since caller has carrier privilege + return true; + } catch (SecurityException carrierPrivilegeException) { + } + } } - } catch (SecurityException | PackageManager.NameNotFoundException e) { } + // Can be read with READ_SMS too. try { context.enforcePermission(android.Manifest.permission.READ_SMS, pid, uid, message); -- cgit v1.2.3 From f17894f341a4a29d791901d37e396db6a02dabb2 Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Wed, 25 Mar 2020 20:29:27 -0700 Subject: Clean up device identifier access logging This commit removes the log message from DevicePolicyManagerService when a caller fails the access requirements as it can be confusing if the caller subsequently passes a carrier privilege check and can access identifiers, or in the case where the caller does not have access a similar entry is logged by TelephonyPermissions. The subId for which the carrier privilege check is performed is also logged to facilitate debugging. Bug: 152117976 Test: atest SubscriptionControllerTest Change-Id: I6d88d739a0d9053e8eff32d74d90009699abe8fc --- .../common/com/android/internal/telephony/TelephonyPermissions.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 0b331744d922..3bf3444d4197 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -361,7 +361,8 @@ public final class TelephonyPermissions { TelephonyCommonStatsLog.write(TelephonyCommonStatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, callingPackage, message, /* isPreinstalled= */ false, false); } - Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message); + Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + ":" + + subId); // if the target SDK is pre-Q then check if the calling package would have previously // had access to device identifiers. if (callingPackageInfo != null && ( -- cgit v1.2.3 From 7e826f9531a25edbca6e0229c50807a634c430b2 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Wed, 1 Apr 2020 18:00:09 -0700 Subject: Fix order of uid/pid in LocationAccessPolicy Fix the order in which uid and pid are passed into the permission check. Test: atest LocationAccessPolicyTest Fixes: 151330809 Change-Id: I479c8fc123d5a994e8cbe6489aa00bea4abca1c7 (cherry picked from commit 0cd4dbc0b3993f7e48c4a30ce715e5405fce4fbf) --- telephony/common/android/telephony/LocationAccessPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'telephony/common') diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f3e9de0d2688..3048ad7c1fb0 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -311,7 +311,7 @@ public final class LocationAccessPolicy { } // If the user or profile is current, permission is granted. // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid); } private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { -- cgit v1.2.3 From 851e1ad2097fbcd1b648bab1e5914512f0b4dd0d Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Fri, 1 May 2020 15:24:52 -0700 Subject: Add a helper function to query CBR package name. Test: manual Bug: 154436403 Change-Id: Id1e77abc4df88a3d5c3f28b366a7dfb415a2bbb0 Merged-in: Id1e77abc4df88a3d5c3f28b366a7dfb415a2bbb0 --- .../internal/telephony/CellBroadcastUtils.java | 65 ++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 telephony/common/com/android/internal/telephony/CellBroadcastUtils.java (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java new file mode 100644 index 000000000000..6c6375586225 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 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.internal.telephony; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.Telephony; +import android.text.TextUtils; +import android.util.Log; + +/** + * This class provides utility functions related to CellBroadcast. + */ +public class CellBroadcastUtils { + private static final String TAG = "CellBroadcastUtils"; + private static final boolean VDBG = false; + + /** + * Utility method to query the default CBR's package name. + */ + public static String getDefaultCellBroadcastReceiverPackageName(Context context) { + PackageManager packageManager = context.getPackageManager(); + ResolveInfo resolveInfo = packageManager.resolveActivity( + new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION), + PackageManager.MATCH_SYSTEM_ONLY); + String packageName; + + if (resolveInfo == null) { + Log.e(TAG, "getDefaultCellBroadcastReceiverPackageName: no package found"); + return null; + } + + packageName = resolveInfo.activityInfo.applicationInfo.packageName; + + if (VDBG) { + Log.d(TAG, "getDefaultCellBroadcastReceiverPackageName: found package: " + packageName); + } + + if (TextUtils.isEmpty(packageName) || packageManager.checkPermission( + android.Manifest.permission.READ_CELL_BROADCASTS, packageName) + == PackageManager.PERMISSION_DENIED) { + Log.e(TAG, "getDefaultCellBroadcastReceiverPackageName: returning null; " + + "permission check failed for : " + packageName); + return null; + } + + return packageName; + } +} -- cgit v1.2.3 From 1eff8d2232c68cfa62b281d189656a23c927b151 Mon Sep 17 00:00:00 2001 From: Chen Xu Date: Wed, 13 May 2020 09:17:06 -0700 Subject: allow CBR to write SMS database This is to support KR government requirement: CBR message should be available in SMS.inbox. The solution is to insert CBR message once displayed to users. this feature is disabled by default and OEM can turn it on for specific mcc per country/carrier regulations. Bug: 144749813 Test: Manual test on different messenger apps. Change-Id: I5535cefb32e89694094299f3b89321f735983ef1 --- .../android/internal/telephony/SmsApplication.java | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index bb6f154335a9..b35b3236afc6 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -536,13 +536,16 @@ public final class SmsApplication { // Assign permission to special system apps assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - PHONE_PACKAGE_NAME); + PHONE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - BLUETOOTH_PACKAGE_NAME); + BLUETOOTH_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - MMS_SERVICE_PACKAGE_NAME); + MMS_SERVICE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - TELEPHONY_PROVIDER_PACKAGE_NAME); + TELEPHONY_PROVIDER_PACKAGE_NAME, true); + // CellbroadcastReceiver is a mainline module thus skip signature match. + assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, + CellBroadcastUtils.getDefaultCellBroadcastReceiverPackageName(context), false); // Give AppOps permission to UID 1001 which contains multiple // apps, all of them should be able to write to telephony provider. @@ -744,17 +747,23 @@ public final class SmsApplication { * @param packageManager The package manager instance * @param appOps The AppOps manager instance * @param packageName The package name of the system app + * @param sigatureMatch whether to check signature match */ private static void assignExclusiveSmsPermissionsToSystemApp(Context context, - PackageManager packageManager, AppOpsManager appOps, String packageName) { + PackageManager packageManager, AppOpsManager appOps, String packageName, + boolean sigatureMatch) { // First check package signature matches the caller's package signature. // Since this class is only used internally by the system, this check makes sure // the package signature matches system signature. - final int result = packageManager.checkSignatures(context.getPackageName(), packageName); - if (result != PackageManager.SIGNATURE_MATCH) { - Log.e(LOG_TAG, packageName + " does not have system signature"); - return; + if (sigatureMatch) { + final int result = packageManager.checkSignatures(context.getPackageName(), + packageName); + if (result != PackageManager.SIGNATURE_MATCH) { + Log.e(LOG_TAG, packageName + " does not have system signature"); + return; + } } + try { PackageInfo info = packageManager.getPackageInfo(packageName, 0); int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid, -- cgit v1.2.3 From c4b36e9d003111e14c2c96db736e3044431661e8 Mon Sep 17 00:00:00 2001 From: Andrew Sapperstein Date: Tue, 9 Jun 2020 16:52:56 +0000 Subject: Revert "Add a blacklist for preinstalled carrier apps." This reverts commit 1a3158b8e17cfe81e4576c9f64522704de892481. Reason for revert: The carrier for which this was needed has been updated to no longer require this change. Change-Id: I5d07bd7ce4bde11a287f44ad2ff97370d4c30db6 Merged-In: I5d07bd7ce4bde11a287f44ad2ff97370d4c30db6 Bug: 138150105 Fixes: 157957579 Test: verify that app is no longer there --- .../common/com/android/internal/telephony/CarrierAppUtils.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index b3d7c0d36763..4606fb4b631c 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -21,7 +21,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.os.SystemConfigManager; import android.os.UserHandle; import android.permission.PermissionManager; @@ -30,9 +29,7 @@ import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.util.ArrayUtils; import java.util.ArrayList; import java.util.List; @@ -162,12 +159,9 @@ public final class CarrierAppUtils { try { for (ApplicationInfo ai : candidates) { String packageName = ai.packageName; - String[] restrictedCarrierApps = Resources.getSystem().getStringArray( - R.array.config_restrictedPreinstalledCarrierApps); boolean hasPrivileges = telephonyManager != null && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS - && !ArrayUtils.contains(restrictedCarrierApps, packageName); + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; // add hiddenUntilInstalled flag for carrier apps and associated apps packageManager.setSystemAppState( -- cgit v1.2.3 From 87cd4b076176539d5e3520e4a10dfaa380380c52 Mon Sep 17 00:00:00 2001 From: Hunter Knepshield Date: Fri, 12 Jun 2020 15:02:39 -0700 Subject: Reevaluate some carrier-associated apps' status on SDK changes. If a carrier-associated app is added to the system image after the device is initially launched (e.g. Q -> R OTA), the logic to disable apps without a corresponding SIM will not run, leaving the app in an enabled state in more cases than there should be. To safely account for this, we only reevaluate on SDK change for apps whose addedInSdk value falls within the range since the last time we evaluated carrier apps' statuses. Apps that set lower (or higher) SDK versions are ignored. We choose *not* to use minimum or target SDK version here because it's more likely to change with each OTA / prebuilt, which could cause unintended disable operations (or lack thereof). Bug: 154872019 Test: manual, QA, atest FrameworksTelephonyTests:CarrierAppUtilsTest Change-Id: I97ddfa03f34e7d00e66ce7cd508d6d2a5e83fbc2 --- .../internal/telephony/CarrierAppUtils.java | 158 +++++++++++++-------- 1 file changed, 102 insertions(+), 56 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 4606fb4b631c..e57b03098758 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -21,6 +21,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.Build; +import android.os.CarrierAssociatedAppEntry; import android.os.SystemConfigManager; import android.os.UserHandle; import android.permission.PermissionManager; @@ -79,8 +81,8 @@ public final class CarrierAppUtils { SystemConfigManager config = context.getSystemService(SystemConfigManager.class); Set systemCarrierAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierApps(); - Map> systemCarrierAssociatedAppsDisabledUntilUsed = - config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + Map> systemCarrierAssociatedAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, @@ -107,8 +109,8 @@ public final class CarrierAppUtils { Set systemCarrierAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierApps(); - Map> systemCarrierAssociatedAppsDisabledUntilUsed = - config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + Map> systemCarrierAssociatedAppsDisabledUntilUsed = + config.getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, null /* telephonyManager */, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, @@ -138,8 +140,8 @@ public final class CarrierAppUtils { public static void disableCarrierAppsUntilPrivileged(String callingPackage, @Nullable TelephonyManager telephonyManager, ContentResolver contentResolver, int userId, Set systemCarrierAppsDisabledUntilUsed, - Map> systemCarrierAssociatedAppsDisabledUntilUsed, - Context context) { + Map> + systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { PackageManager packageManager = context.getPackageManager(); PermissionManager permissionManager = (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); @@ -149,12 +151,17 @@ public final class CarrierAppUtils { return; } - Map> associatedApps = getDefaultCarrierAssociatedAppsHelper( + Map> associatedApps = getDefaultCarrierAssociatedAppsHelper( userId, systemCarrierAssociatedAppsDisabledUntilUsed, context); List enabledCarrierPackages = new ArrayList<>(); - boolean hasRunOnce = Settings.Secure.getInt(contentResolver, - Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1; + int carrierAppsHandledSdk = + Settings.Secure.getInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0); + if (DEBUG) { + Log.i(TAG, "Last execution SDK: " + carrierAppsHandledSdk); + } + boolean hasRunEver = carrierAppsHandledSdk != 0; // SDKs < R used to just set 1 here + boolean hasRunForSdk = carrierAppsHandledSdk == Build.VERSION.SDK_INT; try { for (ApplicationInfo ai : candidates) { @@ -166,10 +173,10 @@ public final class CarrierAppUtils { // add hiddenUntilInstalled flag for carrier apps and associated apps packageManager.setSystemAppState( packageName, PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN); - List associatedAppList = associatedApps.get(packageName); + List associatedAppList = associatedApps.get(packageName); if (associatedAppList != null) { - for (ApplicationInfo associatedApp : associatedAppList) { - packageManager.setSystemAppState(associatedApp.packageName, + for (AssociatedAppInfo associatedApp : associatedAppList) { + packageManager.setSystemAppState(associatedApp.appInfo.packageName, PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN); } } @@ -184,7 +191,7 @@ public final class CarrierAppUtils { || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + Log.i(TAG, "Update state (" + packageName + "): ENABLED for user " + userId); context.createContextAsUser(UserHandle.of(userId), 0) .getPackageManager() @@ -200,28 +207,37 @@ public final class CarrierAppUtils { // Also enable any associated apps for this carrier app. if (associatedAppList != null) { - for (ApplicationInfo associatedApp : associatedAppList) { + for (AssociatedAppInfo associatedApp : associatedAppList) { int associatedAppEnabledSetting = context .createContextAsUser(UserHandle.of(userId), 0) .getPackageManager() - .getApplicationEnabledSetting(associatedApp.packageName); + .getApplicationEnabledSetting( + associatedApp.appInfo.packageName); + boolean associatedAppInstalled = (associatedApp.appInfo.flags + & ApplicationInfo.FLAG_INSTALLED) != 0; + if (DEBUG) { + Log.i(TAG, "(hasPrivileges) associated app " + + associatedApp.appInfo.packageName + ", enabled = " + + associatedAppEnabledSetting + ", installed = " + + associatedAppInstalled); + } if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED - || (associatedApp.flags - & ApplicationInfo.FLAG_INSTALLED) == 0) { - Log.i(TAG, "Update associated state(" + associatedApp.packageName - + "): ENABLED for user " + userId); + || !associatedAppInstalled) { + Log.i(TAG, "Update associated state (" + + associatedApp.appInfo.packageName + "): ENABLED for user " + + userId); context.createContextAsUser(UserHandle.of(userId), 0) .getPackageManager() - .setSystemAppState(associatedApp.packageName, + .setSystemAppState(associatedApp.appInfo.packageName, PackageManager.SYSTEM_APP_STATE_INSTALLED); context.createPackageContextAsUser( callingPackage, 0, UserHandle.of(userId)) .getPackageManager() .setApplicationEnabledSetting( - associatedApp.packageName, + associatedApp.appInfo.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } @@ -236,7 +252,7 @@ public final class CarrierAppUtils { if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Log.i(TAG, "Update state(" + packageName + Log.i(TAG, "Update state (" + packageName + "): DISABLED_UNTIL_USED for user " + userId); context.createContextAsUser(UserHandle.of(userId), 0) .getPackageManager() @@ -244,37 +260,56 @@ public final class CarrierAppUtils { packageName, PackageManager.SYSTEM_APP_STATE_UNINSTALLED); } - // Also disable any associated apps for this carrier app if this is the first - // run. We avoid doing this a second time because it is brittle to rely on the - // distinction between "default" and "enabled". - if (!hasRunOnce) { - if (associatedAppList != null) { - for (ApplicationInfo associatedApp : associatedAppList) { - int associatedAppEnabledSetting = context - .createContextAsUser(UserHandle.of(userId), 0) + // Associated apps are more brittle, because we can't rely on the distinction + // between "default" and "enabled". To account for this, we have two cases: + // 1. We've never run before, so we're fine to disable all associated apps. + // 2. We've run before, but not on this SDK version, so we will only operate on + // apps with addedInSdk in the range (lastHandledSdk, currentSdk]. + // Otherwise, don't touch the associated apps. + if (associatedAppList != null) { + for (AssociatedAppInfo associatedApp : associatedAppList) { + boolean allowDisable = !hasRunEver || (!hasRunForSdk + && associatedApp.addedInSdk + != CarrierAssociatedAppEntry.SDK_UNSPECIFIED + && associatedApp.addedInSdk > carrierAppsHandledSdk + && associatedApp.addedInSdk <= Build.VERSION.SDK_INT); + int associatedAppEnabledSetting = context + .createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getApplicationEnabledSetting( + associatedApp.appInfo.packageName); + boolean associatedAppInstalled = (associatedApp.appInfo.flags + & ApplicationInfo.FLAG_INSTALLED) != 0; + if (DEBUG) { + Log.i(TAG, "(!hasPrivileges) associated app " + + associatedApp.appInfo.packageName + ", allowDisable = " + + allowDisable + ", addedInSdk = " + + associatedApp.addedInSdk + ", enabled = " + + associatedAppEnabledSetting + ", installed = " + + associatedAppInstalled); + } + if (allowDisable + && associatedAppEnabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + && associatedAppInstalled) { + Log.i(TAG, + "Update associated state (" + + associatedApp.appInfo.packageName + + "): DISABLED_UNTIL_USED for user " + userId); + context.createContextAsUser(UserHandle.of(userId), 0) .getPackageManager() - .getApplicationEnabledSetting(associatedApp.packageName); - if (associatedAppEnabledSetting - == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - && (associatedApp.flags - & ApplicationInfo.FLAG_INSTALLED) != 0) { - Log.i(TAG, - "Update associated state(" + associatedApp.packageName - + "): DISABLED_UNTIL_USED for user " + userId); - context.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .setSystemAppState(associatedApp.packageName, - PackageManager.SYSTEM_APP_STATE_UNINSTALLED); - } + .setSystemAppState(associatedApp.appInfo.packageName, + PackageManager.SYSTEM_APP_STATE_UNINSTALLED); } } } } } - // Mark the execution so we do not disable apps again. - if (!hasRunOnce) { - Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1); + // Mark the execution so we do not disable apps again on this SDK version. + if (!hasRunEver || !hasRunForSdk) { + Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, + Build.VERSION.SDK_INT); } if (!enabledCarrierPackages.isEmpty()) { @@ -360,28 +395,28 @@ public final class CarrierAppUtils { return apps; } - private static Map> getDefaultCarrierAssociatedAppsHelper( - int userId, Map> systemCarrierAssociatedAppsDisabledUntilUsed, - Context context) { + private static Map> getDefaultCarrierAssociatedAppsHelper( + int userId, Map> + systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { int size = systemCarrierAssociatedAppsDisabledUntilUsed.size(); - Map> associatedApps = new ArrayMap<>(size); - for (Map.Entry> entry + Map> associatedApps = new ArrayMap<>(size); + for (Map.Entry> entry : systemCarrierAssociatedAppsDisabledUntilUsed.entrySet()) { String carrierAppPackage = entry.getKey(); - List associatedAppPackages = entry.getValue(); + List associatedAppPackages = entry.getValue(); for (int j = 0; j < associatedAppPackages.size(); j++) { + CarrierAssociatedAppEntry associatedApp = associatedAppPackages.get(j); ApplicationInfo ai = - getApplicationInfoIfSystemApp( - userId, associatedAppPackages.get(j), context); + getApplicationInfoIfSystemApp(userId, associatedApp.packageName, context); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. if (ai != null && !isUpdatedSystemApp(ai)) { - List appList = associatedApps.get(carrierAppPackage); + List appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); associatedApps.put(carrierAppPackage, appList); } - appList.add(ai); + appList.add(new AssociatedAppInfo(ai, associatedApp.addedInSdk)); } } } @@ -406,4 +441,15 @@ public final class CarrierAppUtils { } return null; } + + private static final class AssociatedAppInfo { + public final ApplicationInfo appInfo; + // Might be CarrierAssociatedAppEntry.SDK_UNSPECIFIED. + public final int addedInSdk; + + AssociatedAppInfo(ApplicationInfo appInfo, int addedInSdk) { + this.appInfo = appInfo; + this.addedInSdk = addedInSdk; + } + } } -- cgit v1.2.3 From 8d7d92d71055e7740fe1ba993960774070aea6f5 Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Sat, 20 Jun 2020 15:25:53 -0700 Subject: Reorder TelephonyPermissions calls for carrier privileges The SubscriptionManager APIs that return SubscriptionInfo objects are often invoked by carrier privileged apps to obtain the details about the subscription(s). Identifier and phone number access checks currently verify requirements that typically cannot be satisfied by carrier privileged apps first before verifying an app holds carrier privileges. This commit invokes the carrier privileges check before the generic PermissionManagerService check for identifier access and moves the WRITE_SMS appop check after the READ_PHONE_STATE / carrier privilege check for phone number access. Bug: 157642567 Fixes: 73308711 Test: atest TelephonyPermissionsTest Test: atest SubscriptionControllerTest Change-Id: I0a446af5c2adaf1d6b06da221f9e236b1bdde146 --- .../internal/telephony/TelephonyPermissions.java | 43 +++++++++++----------- 1 file changed, 21 insertions(+), 22 deletions(-) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 1a38a42873b7..bc987a6282c7 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -303,12 +303,6 @@ public final class TelephonyPermissions { String message, boolean allowCarrierPrivilegeOnAnySub) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); - PermissionManager permissionManager = (PermissionManager) context.getSystemService( - Context.PERMISSION_SERVICE); - if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId, - pid, uid) == PackageManager.PERMISSION_GRANTED) { - return true; - } // If the calling package has carrier privileges for specified sub, then allow access. if (checkCarrierPrivilegeForSubId(context, subId)) return true; @@ -319,6 +313,13 @@ public final class TelephonyPermissions { return true; } + PermissionManager permissionManager = (PermissionManager) context.getSystemService( + Context.PERMISSION_SERVICE); + if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId, + pid, uid) == PackageManager.PERMISSION_GRANTED) { + return true; + } + return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage, message); } @@ -433,16 +434,6 @@ public final class TelephonyPermissions { public static boolean checkReadPhoneNumber( Context context, int subId, int pid, int uid, String callingPackage, @Nullable String callingFeatureId, String message) { - // Default SMS app can always read it. - AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - if (appOps.noteOp(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage, callingFeatureId, - null) == AppOpsManager.MODE_ALLOWED) { - return true; - } - - // NOTE(b/73308711): If an app has one of the following AppOps bits explicitly revoked, they - // will be denied access, even if they have another permission and AppOps bit if needed. - // First, check if the SDK version is below R boolean preR = false; try { @@ -477,21 +468,29 @@ public final class TelephonyPermissions { } } + // Default SMS app can always read it. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + if (appOps.noteOp(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage, callingFeatureId, + null) == AppOpsManager.MODE_ALLOWED) { + return true; + } // Can be read with READ_SMS too. try { context.enforcePermission(android.Manifest.permission.READ_SMS, pid, uid, message); - return appOps.noteOp(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage, - callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; - + if (appOps.noteOp(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED) { + return true; + } } catch (SecurityException readSmsSecurityException) { } // Can be read with READ_PHONE_NUMBERS too. try { context.enforcePermission(android.Manifest.permission.READ_PHONE_NUMBERS, pid, uid, message); - return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, - callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; - + if (appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED) { + return true; + } } catch (SecurityException readPhoneNumberSecurityException) { } -- cgit v1.2.3 From 12a3f5cfdcc79f7a829bb2c96492c36f6d3ca42d Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Mon, 13 Jul 2020 12:42:36 -0700 Subject: Skip carrier priv check for trusted UIDs Checking carrier privileges for UIDs with lots of shared apps can incur a significant performance hit. For UIDs that are fixed and trusted (system and phone), skip the permission check and always allow. Also, double the cache size for getPackageInfo in order to reduce the rate of cache misses. Bug: 160971853 Test: manual verification -- observed lower rate of cache misses for getPackageInfo from com.android.phone. Change-Id: I1399cab579308479d7cf191b8795441cbcd3ff65 --- .../common/com/android/internal/telephony/TelephonyPermissions.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'telephony/common') diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index bc987a6282c7..71a1964210b0 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -623,6 +623,10 @@ public final class TelephonyPermissions { } private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) { + if (uid == Process.SYSTEM_UID || uid == Process.PHONE_UID) { + // Skip the check if it's one of these special uids + return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } final long identity = Binder.clearCallingIdentity(); try { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( -- cgit v1.2.3