diff options
36 files changed, 1652 insertions, 91 deletions
diff --git a/api/current.xml b/api/current.xml index 7dc498d..69d21a5 100644 --- a/api/current.xml +++ b/api/current.xml @@ -55702,17 +55702,6 @@ <parameter name="message" type="java.lang.String"> </parameter> </constructor> -<field name="TYPE_DRM_INFO_ACQUISITION_FAILED" - type="int" - transient="false" - volatile="false" - value="2008" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="TYPE_NOT_SUPPORTED" type="int" transient="false" @@ -55846,17 +55835,6 @@ visibility="public" > </method> -<field name="DRM_INFO_OBJECT" - type="java.lang.String" - transient="false" - volatile="false" - value=""drm_info_object"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="DRM_INFO_STATUS_OBJECT" type="java.lang.String" transient="false" @@ -55879,17 +55857,6 @@ visibility="public" > </field> -<field name="TYPE_DRM_INFO_ACQUIRED" - type="int" - transient="false" - volatile="false" - value="1003" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="TYPE_DRM_INFO_PROCESSED" type="int" transient="false" @@ -56365,6 +56332,19 @@ </parameter> </constructor> <method name="acquireDrmInfo" + return="android.drm.DrmInfo" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="drmInfoRequest" type="android.drm.DrmInfoRequest"> +</parameter> +</method> +<method name="acquireRights" return="int" abstract="false" native="false" @@ -56562,6 +56542,32 @@ <parameter name="mimeType" type="java.lang.String"> </parameter> </method> +<method name="getMetadata" + return="android.content.ContentValues" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="java.lang.String"> +</parameter> +</method> +<method name="getMetadata" + return="android.content.ContentValues" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</method> <method name="getOriginalMimeType" return="java.lang.String" abstract="false" diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 2fb746c..9a3d621 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -1107,6 +1107,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @return true if a successful launch, false if could not (e.g. bad position). */ protected boolean launchSuggestion(int position, int actionKey, String actionMsg) { + if (mSuggestionsAdapter == null) { + return false; + } Cursor c = mSuggestionsAdapter.getCursor(); if ((c != null) && c.moveToPosition(position)) { diff --git a/drm/common/Android.mk b/drm/common/Android.mk index 808b2c2..c79a91a 100644 --- a/drm/common/Android.mk +++ b/drm/common/Android.mk @@ -18,6 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ DrmConstraints.cpp \ + DrmMetadata.cpp \ DrmConvertedStatus.cpp \ DrmEngineBase.cpp \ DrmInfo.cpp \ diff --git a/drm/common/DrmEngineBase.cpp b/drm/common/DrmEngineBase.cpp index 10c64ee..ac360eb 100644 --- a/drm/common/DrmEngineBase.cpp +++ b/drm/common/DrmEngineBase.cpp @@ -31,6 +31,10 @@ DrmConstraints* DrmEngineBase::getConstraints( return onGetConstraints(uniqueId, path, action); } +DrmMetadata* DrmEngineBase::getMetadata(int uniqueId, const String8* path) { + return onGetMetadata(uniqueId, path); +} + status_t DrmEngineBase::initialize(int uniqueId) { return onInitialize(uniqueId); } diff --git a/drm/common/DrmMetadata.cpp b/drm/common/DrmMetadata.cpp new file mode 100644 index 0000000..6cc5ec1 --- /dev/null +++ b/drm/common/DrmMetadata.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <drm/DrmMetadata.h> + +using namespace android; + +int DrmMetadata::getCount(void) const { + return mMetadataMap.size(); +} + +status_t DrmMetadata::put(const String8* key, + const char* value) { + if((value != NULL) && (key != NULL)) { + int length = strlen(value); + char* charValue = new char[length + 1]; + + memcpy(charValue, value, length); + charValue[length] = '\0'; + mMetadataMap.add(*key, charValue); + } + return NO_ERROR; +} + +String8 DrmMetadata::get(const String8& key) const { + if (NULL != getValue(&key)) { + return String8(getValue(&key)); + } + else { + return String8(""); + } +} + +const char* DrmMetadata::getValue(const String8* key) const { + if(key != NULL) { + if (NAME_NOT_FOUND != mMetadataMap.indexOfKey(*key)) { + return mMetadataMap.valueFor(*key); + } + else { + return NULL; + } + } else { + return NULL; + } +} + +const char* DrmMetadata::getAsByteArray(const String8* key) const { + return getValue(key); +} + +bool DrmMetadata::KeyIterator::hasNext() { + return mIndex < mDrmMetadata->mMetadataMap.size(); +} + +const String8& DrmMetadata::KeyIterator::next() { + const String8& key = mDrmMetadata->mMetadataMap.keyAt(mIndex); + mIndex++; + return key; +} + +DrmMetadata::KeyIterator DrmMetadata::keyIterator() { + return KeyIterator(this); +} + +DrmMetadata::KeyIterator::KeyIterator(const DrmMetadata::KeyIterator& keyIterator) : + mDrmMetadata(keyIterator.mDrmMetadata), + mIndex(keyIterator.mIndex) { + LOGV("DrmMetadata::KeyIterator::KeyIterator"); +} + +DrmMetadata::KeyIterator& DrmMetadata::KeyIterator::operator=(const DrmMetadata::KeyIterator& keyIterator) { + LOGV("DrmMetadata::KeyIterator::operator="); + mDrmMetadata = keyIterator.mDrmMetadata; + mIndex = keyIterator.mIndex; + return *this; +} + + +DrmMetadata::Iterator DrmMetadata::iterator() { + return Iterator(this); +} + +DrmMetadata::Iterator::Iterator(const DrmMetadata::Iterator& iterator) : + mDrmMetadata(iterator.mDrmMetadata), + mIndex(iterator.mIndex) { + LOGV("DrmMetadata::Iterator::Iterator"); +} + +DrmMetadata::Iterator& DrmMetadata::Iterator::operator=(const DrmMetadata::Iterator& iterator) { + LOGV("DrmMetadata::Iterator::operator="); + mDrmMetadata = iterator.mDrmMetadata; + mIndex = iterator.mIndex; + return *this; +} + +bool DrmMetadata::Iterator::hasNext() { + return mIndex < mDrmMetadata->mMetadataMap.size(); +} + +String8 DrmMetadata::Iterator::next() { + String8 value = String8(mDrmMetadata->mMetadataMap.editValueAt(mIndex)); + mIndex++; + return value; +} diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp index b8ae852..723b50e 100644 --- a/drm/common/IDrmManagerService.cpp +++ b/drm/common/IDrmManagerService.cpp @@ -24,6 +24,7 @@ #include <drm/DrmInfo.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmRights.h> #include <drm/DrmInfoStatus.h> #include <drm/DrmConvertedStatus.h> @@ -123,6 +124,35 @@ DrmConstraints* BpDrmManagerService::getConstraints( return drmConstraints; } +DrmMetadata* BpDrmManagerService::getMetadata(int uniqueId, const String8* path) { + LOGV("Get Metadata"); + Parcel data, reply; + data.writeInterfaceToken(IDrmManagerService::getInterfaceDescriptor()); + data.writeInt32(uniqueId); + + DrmMetadata* drmMetadata = NULL; + data.writeString8(*path); + remote()->transact(GET_METADATA_FROM_CONTENT, data, &reply); + + if (0 != reply.dataAvail()) { + //Filling Drm Metadata + drmMetadata = new DrmMetadata(); + + const int size = reply.readInt32(); + for (int index = 0; index < size; ++index) { + const String8 key(reply.readString8()); + const int bufferSize = reply.readInt32(); + char* data = NULL; + if (0 < bufferSize) { + data = new char[bufferSize]; + reply.read(data, bufferSize); + } + drmMetadata->put(&key, data); + } + } + return drmMetadata; +} + bool BpDrmManagerService::canHandle(int uniqueId, const String8& path, const String8& mimeType) { LOGV("Can Handle"); Parcel data, reply; @@ -827,6 +857,38 @@ status_t BnDrmManagerService::onTransact( return DRM_NO_ERROR; } + case GET_METADATA_FROM_CONTENT: + { + LOGV("BnDrmManagerService::onTransact :GET_METADATA_FROM_CONTENT"); + CHECK_INTERFACE(IDrmManagerService, data, reply); + + const int uniqueId = data.readInt32(); + const String8 path = data.readString8(); + + DrmMetadata* drmMetadata = getMetadata(uniqueId, &path); + if (NULL != drmMetadata) { + //Filling DRM Metadata contents + reply->writeInt32(drmMetadata->getCount()); + + DrmMetadata::KeyIterator keyIt = drmMetadata->keyIterator(); + while (keyIt.hasNext()) { + const String8 key = keyIt.next(); + reply->writeString8(key); + const char* value = drmMetadata->getAsByteArray(&key); + int bufferSize = 0; + if (NULL != value) { + bufferSize = strlen(value); + reply->writeInt32(bufferSize + 1); + reply->write(value, bufferSize + 1); + } else { + reply->writeInt32(0); + } + } + } + delete drmMetadata; drmMetadata = NULL; + return NO_ERROR; + } + case CAN_HANDLE: { LOGV("BnDrmManagerService::onTransact :CAN_HANDLE"); diff --git a/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp index b7a035f..537791c 100644 --- a/drm/drmserver/DrmManager.cpp +++ b/drm/drmserver/DrmManager.cpp @@ -23,6 +23,7 @@ #include <drm/DrmInfoEvent.h> #include <drm/DrmRights.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmInfoStatus.h> #include <drm/DrmInfoRequest.h> #include <drm/DrmSupportInfo.h> @@ -148,6 +149,15 @@ DrmConstraints* DrmManager::getConstraints(int uniqueId, const String8* path, co return NULL; } +DrmMetadata* DrmManager::getMetadata(int uniqueId, const String8* path) { + const String8 plugInId = getSupportedPlugInIdFromPath(uniqueId, *path); + if (EMPTY_STRING != plugInId) { + IDrmEngine& rDrmEngine = mPlugInManager.getPlugIn(plugInId); + return rDrmEngine.getMetadata(uniqueId, path); + } + return NULL; +} + status_t DrmManager::installDrmEngine(int uniqueId, const String8& absolutePath) { mPlugInManager.loadPlugIn(absolutePath); diff --git a/drm/drmserver/DrmManagerService.cpp b/drm/drmserver/DrmManagerService.cpp index 8cf510d..4dcfa72 100644 --- a/drm/drmserver/DrmManagerService.cpp +++ b/drm/drmserver/DrmManagerService.cpp @@ -18,18 +18,50 @@ #define LOG_TAG "DrmManagerService(Native)" #include <utils/Log.h> +#include <private/android_filesystem_config.h> + #include <errno.h> #include <utils/threads.h> #include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> #include <sys/stat.h> #include "DrmManagerService.h" #include "DrmManager.h" using namespace android; +static Vector<uid_t> trustedUids; + +static bool isProtectedCallAllowed() { + // TODO + // Following implementation is just for reference. + // Each OEM manufacturer should implement/replace with their own solutions. + bool result = false; + + IPCThreadState* ipcState = IPCThreadState::self(); + uid_t uid = ipcState->getCallingUid(); + + for (unsigned int i = 0; i < trustedUids.size(); ++i) { + if (trustedUids[i] == uid) { + result = true; + break; + } + } + return result; +} + void DrmManagerService::instantiate() { LOGV("instantiate"); defaultServiceManager()->addService(String16("drm.drmManager"), new DrmManagerService()); + + if (0 >= trustedUids.size()) { + // TODO + // Following implementation is just for reference. + // Each OEM manufacturer should implement/replace with their own solutions. + + // Add trusted uids here + trustedUids.push(AID_MEDIA); + } } DrmManagerService::DrmManagerService() : @@ -79,6 +111,11 @@ DrmConstraints* DrmManagerService::getConstraints( return mDrmManager->getConstraints(uniqueId, path, action); } +DrmMetadata* DrmManagerService::getMetadata(int uniqueId, const String8* path) { + LOGV("Entering getMetadata from content"); + return mDrmManager->getMetadata(uniqueId, path); +} + bool DrmManagerService::canHandle(int uniqueId, const String8& path, const String8& mimeType) { LOGV("Entering canHandle"); return mDrmManager->canHandle(uniqueId, path, mimeType); @@ -172,13 +209,21 @@ status_t DrmManagerService::getAllSupportInfo( DecryptHandle* DrmManagerService::openDecryptSession( int uniqueId, int fd, int offset, int length) { LOGV("Entering DrmManagerService::openDecryptSession"); - return mDrmManager->openDecryptSession(uniqueId, fd, offset, length); + if (isProtectedCallAllowed()) { + return mDrmManager->openDecryptSession(uniqueId, fd, offset, length); + } + + return NULL; } DecryptHandle* DrmManagerService::openDecryptSession( int uniqueId, const char* uri) { LOGV("Entering DrmManagerService::openDecryptSession with uri"); - return mDrmManager->openDecryptSession(uniqueId, uri); + if (isProtectedCallAllowed()) { + return mDrmManager->openDecryptSession(uniqueId, uri); + } + + return NULL; } status_t DrmManagerService::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { diff --git a/drm/java/android/drm/DrmErrorEvent.java b/drm/java/android/drm/DrmErrorEvent.java index 9294884..20fd8aa 100644 --- a/drm/java/android/drm/DrmErrorEvent.java +++ b/drm/java/android/drm/DrmErrorEvent.java @@ -53,11 +53,6 @@ public class DrmErrorEvent extends DrmEvent { * associated with all DRM schemes. */ public static final int TYPE_REMOVE_ALL_RIGHTS_FAILED = 2007; - /** - * TYPE_DRM_INFO_ACQUISITION_FAILED, when failed to get the required information to - * communicate with the service. - */ - public static final int TYPE_DRM_INFO_ACQUISITION_FAILED = 2008; /** * constructor to create DrmErrorEvent object with given parameters diff --git a/drm/java/android/drm/DrmEvent.java b/drm/java/android/drm/DrmEvent.java index 583337f..f7bc5cd 100644 --- a/drm/java/android/drm/DrmEvent.java +++ b/drm/java/android/drm/DrmEvent.java @@ -31,14 +31,8 @@ public class DrmEvent { * Constant field signifies that given information is processed successfully */ public static final int TYPE_DRM_INFO_PROCESSED = 1002; - /** - * Constant field signifies that the required information to communicate with - * the service is acquired sucessfully - */ - public static final int TYPE_DRM_INFO_ACQUIRED = 1003; public static final String DRM_INFO_STATUS_OBJECT = "drm_info_status_object"; - public static final String DRM_INFO_OBJECT = "drm_info_object"; private final int mUniqueId; private final int mType; diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index 5044d36..2f54b33 100644 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -102,8 +102,7 @@ public class DrmManagerClient { } private static final int ACTION_REMOVE_ALL_RIGHTS = 1001; - private static final int ACTION_ACQUIRE_DRM_INFO = 1002; - private static final int ACTION_PROCESS_DRM_INFO = 1003; + private static final int ACTION_PROCESS_DRM_INFO = 1002; private int mUniqueId; private int mNativeContext; @@ -126,18 +125,6 @@ public class DrmManagerClient { HashMap<String, Object> attributes = new HashMap<String, Object>(); switch(msg.what) { - case ACTION_ACQUIRE_DRM_INFO: { - final DrmInfoRequest request = (DrmInfoRequest) msg.obj; - DrmInfo drmInfo = _acquireDrmInfo(mUniqueId, request); - if (null != drmInfo) { - attributes.put(DrmEvent.DRM_INFO_OBJECT, drmInfo); - event = new DrmEvent(mUniqueId, DrmEvent.TYPE_DRM_INFO_ACQUIRED, null); - } else { - error = new DrmErrorEvent(mUniqueId, - DrmErrorEvent.TYPE_DRM_INFO_ACQUISITION_FAILED, null); - } - break; - } case ACTION_PROCESS_DRM_INFO: { final DrmInfo drmInfo = (DrmInfo) msg.obj; DrmInfoStatus status = _processDrmInfo(mUniqueId, drmInfo); @@ -243,19 +230,14 @@ public class DrmManagerClient { */ public DrmManagerClient(Context context) { mContext = context; - Looper looper; - if (null != (looper = Looper.myLooper())) { - mInfoHandler = new InfoHandler(looper); - } else if (null != (looper = Looper.getMainLooper())) { - mInfoHandler = new InfoHandler(looper); - } else { - mInfoHandler = null; - } + HandlerThread infoThread = new HandlerThread("DrmManagerClient.InfoHandler"); + infoThread.start(); + mInfoHandler = new InfoHandler(infoThread.getLooper()); - HandlerThread thread = new HandlerThread("DrmManagerClient.EventHandler"); - thread.start(); - mEventHandler = new EventHandler(thread.getLooper()); + HandlerThread eventThread = new HandlerThread("DrmManagerClient.EventHandler"); + eventThread.start(); + mEventHandler = new EventHandler(eventThread.getLooper()); // save the unique id mUniqueId = hashCode(); @@ -335,10 +317,24 @@ public class DrmManagerClient { return _getConstraints(mUniqueId, path, action); } + /** + * Get metadata information from DRM content + * + * @param path Content path from where DRM metadata would be retrieved. + * @return ContentValues instance in which metadata key-value pairs are embedded + * or null in case of failure + */ + public ContentValues getMetadata(String path) { + if (null == path || path.equals("")) { + throw new IllegalArgumentException("Given path is invalid/null"); + } + return _getMetadata(mUniqueId, path); + } + /** * Get constraints information evaluated from DRM content * - * @param uri The Content URI of the data + * @param uri Content URI from where DRM constraints would be retrieved. * @param action Actions defined in {@link DrmStore.Action} * @return ContentValues instance in which constraints key-value pairs are embedded * or null in case of failure @@ -350,6 +346,20 @@ public class DrmManagerClient { return getConstraints(convertUriToPath(uri), action); } + /** + * Get metadata information from DRM content + * + * @param uri Content URI from where DRM metadata would be retrieved. + * @return ContentValues instance in which metadata key-value pairs are embedded + * or null in case of failure + */ + public ContentValues getMetadata(Uri uri) { + if (null == uri || Uri.EMPTY == uri) { + throw new IllegalArgumentException("Uri should be non null"); + } + return getMetadata(convertUriToPath(uri)); + } + /** * Save DRM rights to specified rights path * and make association with content path. @@ -408,7 +418,7 @@ public class DrmManagerClient { /** * Check whether the given mimetype or uri can be handled. * - * @param uri The content URI of the data + * @param uri Content URI of the data to be handled. * @param mimeType Mimetype of the object to be handled * @return * true - if the given mimeType or path can be handled @@ -445,20 +455,31 @@ public class DrmManagerClient { * Retrieves necessary information for register, unregister or rights acquisition. * * @param drmInfoRequest Request information to retrieve drmInfo - * @return - * ERROR_NONE for success - * ERROR_UNKNOWN for failure + * @return DrmInfo Instance as a result of processing given input */ - public int acquireDrmInfo(DrmInfoRequest drmInfoRequest) { + public DrmInfo acquireDrmInfo(DrmInfoRequest drmInfoRequest) { if (null == drmInfoRequest || !drmInfoRequest.isValid()) { throw new IllegalArgumentException("Given drmInfoRequest is invalid/null"); } - int result = ERROR_UNKNOWN; - if (null != mEventHandler) { - Message msg = mEventHandler.obtainMessage(ACTION_ACQUIRE_DRM_INFO, drmInfoRequest); - result = (mEventHandler.sendMessage(msg)) ? ERROR_NONE : result; - } - return result; + return _acquireDrmInfo(mUniqueId, drmInfoRequest); + } + + /** + * Executes given DrmInfoRequest and returns the rights information asynchronously. + * This is a utility API which consists of {@link #acquireDrmInfo(DrmInfoRequest)} + * and {@link #processDrmInfo(DrmInfo)}. + * It can be used if selected DRM agent can work with this combined sequences. + * In case of some DRM schemes, such as OMA DRM, application needs to invoke + * {@link #acquireDrmInfo(DrmInfoRequest)} and {@link #processDrmInfo(DrmInfo)}, separately. + * + * @param drmInfoRequest Request information to retrieve drmInfo + * @return + * ERROR_NONE for success + * ERROR_UNKNOWN for failure + */ + public int acquireRights(DrmInfoRequest drmInfoRequest) { + DrmInfo drmInfo = acquireDrmInfo(drmInfoRequest); + return processDrmInfo(drmInfo); } /** @@ -750,6 +771,8 @@ public class DrmManagerClient { private native ContentValues _getConstraints(int uniqueId, String path, int usage); + private native ContentValues _getMetadata(int uniqueId, String path); + private native boolean _canHandle(int uniqueId, String path, String mimeType); private native DrmInfoStatus _processDrmInfo(int uniqueId, DrmInfo drmInfo); diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp index e5e4547..e131839 100644 --- a/drm/jni/android_drm_DrmManagerClient.cpp +++ b/drm/jni/android_drm_DrmManagerClient.cpp @@ -29,6 +29,7 @@ #include <drm/DrmInfoRequest.h> #include <drm/DrmSupportInfo.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmConvertedStatus.h> #include <drm/drm_framework_common.h> @@ -298,6 +299,43 @@ static jobject android_drm_DrmManagerClient_getConstraintsFromContent( return constraints; } +static jobject android_drm_DrmManagerClient_getMetadataFromContent( + JNIEnv* env, jobject thiz, jint uniqueId, jstring jpath) { + LOGV("GetMetadata - Enter"); + const String8 pathString = Utility::getStringValue(env, jpath); + DrmMetadata* pMetadata = + getDrmManagerClientImpl(env, thiz)->getMetadata(uniqueId, &pathString); + + jobject metadata = NULL; + + jclass localRef = NULL; + localRef = env->FindClass("android/content/ContentValues"); + if (NULL != localRef && NULL != pMetadata) { + // Get the constructor id + jmethodID constructorId = NULL; + constructorId = env->GetMethodID(localRef, "<init>", "()V"); + if (NULL != constructorId) { + // create the java DrmMetadata object + metadata = env->NewObject(localRef, constructorId); + if (NULL != metadata) { + DrmMetadata::KeyIterator keyIt = pMetadata->keyIterator(); + while (keyIt.hasNext()) { + String8 key = keyIt.next(); + // insert the entry<constraintKey, constraintValue> + // to newly created java object + String8 value = pMetadata->get(key); + env->CallVoidMethod(metadata, env->GetMethodID(localRef, "put", + "(Ljava/lang/String;Ljava/lang/String;)V"), + env->NewStringUTF(key.string()), env->NewStringUTF(value.string())); + } + } + } + } + delete pMetadata; pMetadata = NULL; + LOGV("GetMetadata - Exit"); + return metadata; +} + static jobjectArray android_drm_DrmManagerClient_getAllSupportInfo( JNIEnv* env, jobject thiz, jint uniqueId) { LOGV("GetAllSupportInfo - Enter"); @@ -682,6 +720,9 @@ static JNINativeMethod nativeMethods[] = { {"_getConstraints", "(ILjava/lang/String;I)Landroid/content/ContentValues;", (void*)android_drm_DrmManagerClient_getConstraintsFromContent}, + {"_getMetadata", "(ILjava/lang/String;)Landroid/content/ContentValues;", + (void*)android_drm_DrmManagerClient_getMetadataFromContent}, + {"_getAllSupportInfo", "(I)[Landroid/drm/DrmSupportInfo;", (void*)android_drm_DrmManagerClient_getAllSupportInfo}, diff --git a/drm/libdrmframework/DrmManagerClient.cpp b/drm/libdrmframework/DrmManagerClient.cpp index f0439eb..fa3d52a 100644 --- a/drm/libdrmframework/DrmManagerClient.cpp +++ b/drm/libdrmframework/DrmManagerClient.cpp @@ -43,6 +43,10 @@ DrmConstraints* DrmManagerClient::getConstraints(const String8* path, const int return mDrmManagerClientImpl->getConstraints(mUniqueId, path, action); } +DrmMetadata* DrmManagerClient::getMetadata(const String8* path) { + return mDrmManagerClientImpl->getMetadata(mUniqueId, path); +} + bool DrmManagerClient::canHandle(const String8& path, const String8& mimeType) { return mDrmManagerClientImpl->canHandle(mUniqueId, path, mimeType); } diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index b3ae9a7..32fa491 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp @@ -101,6 +101,14 @@ DrmConstraints* DrmManagerClientImpl::getConstraints( return drmConstraints; } +DrmMetadata* DrmManagerClientImpl::getMetadata(int uniqueId, const String8* path) { + DrmMetadata *drmMetadata = NULL; + if ((NULL != path) && (EMPTY_STRING != *path)) { + drmMetadata = getDrmManagerService()->getMetadata(uniqueId, path); + } + return drmMetadata; +} + bool DrmManagerClientImpl::canHandle(int uniqueId, const String8& path, const String8& mimeType) { bool retCode = false; if ((EMPTY_STRING != path) || (EMPTY_STRING != mimeType)) { diff --git a/drm/libdrmframework/include/DrmManager.h b/drm/libdrmframework/include/DrmManager.h index d782f5b..bc462c2 100644 --- a/drm/libdrmframework/include/DrmManager.h +++ b/drm/libdrmframework/include/DrmManager.h @@ -32,6 +32,7 @@ class DrmUnregistrationInfo; class DrmRightsAcquisitionInfo; class DrmContentIds; class DrmConstraints; +class DrmMetadata; class DrmRights; class DrmInfo; class DrmInfoStatus; @@ -74,6 +75,8 @@ public: DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + DrmMetadata* getMetadata(int uniqueId, const String8* path); + bool canHandle(int uniqueId, const String8& path, const String8& mimeType); DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); diff --git a/drm/libdrmframework/include/DrmManagerClientImpl.h b/drm/libdrmframework/include/DrmManagerClientImpl.h index 1c6be46..ff84fc7 100644 --- a/drm/libdrmframework/include/DrmManagerClientImpl.h +++ b/drm/libdrmframework/include/DrmManagerClientImpl.h @@ -86,6 +86,18 @@ public: DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); /** + * Get metadata information associated with input content. + * + * @param[in] uniqueId Unique identifier for a session + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata are embedded in it + * @note + * In case of error, return NULL + */ + DrmMetadata* getMetadata(int uniqueId, const String8* path); + + /** * Check whether the given mimetype or path can be handled * * @param[in] uniqueId Unique identifier for a session diff --git a/drm/libdrmframework/include/DrmManagerService.h b/drm/libdrmframework/include/DrmManagerService.h index 4a3aeae..f346356 100644 --- a/drm/libdrmframework/include/DrmManagerService.h +++ b/drm/libdrmframework/include/DrmManagerService.h @@ -61,6 +61,8 @@ public: DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + DrmMetadata* getMetadata(int uniqueId, const String8* path); + bool canHandle(int uniqueId, const String8& path, const String8& mimeType); DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); diff --git a/drm/libdrmframework/include/IDrmManagerService.h b/drm/libdrmframework/include/IDrmManagerService.h index 1275488..f1dabd3 100644 --- a/drm/libdrmframework/include/IDrmManagerService.h +++ b/drm/libdrmframework/include/IDrmManagerService.h @@ -27,6 +27,7 @@ namespace android { class DrmContentIds; class DrmConstraints; +class DrmMetadata; class DrmRights; class DrmInfo; class DrmInfoStatus; @@ -51,6 +52,7 @@ public: SET_DRM_SERVICE_LISTENER, INSTALL_DRM_ENGINE, GET_CONSTRAINTS_FROM_CONTENT, + GET_METADATA_FROM_CONTENT, CAN_HANDLE, PROCESS_DRM_INFO, ACQUIRE_DRM_INFO, @@ -96,6 +98,8 @@ public: virtual DrmConstraints* getConstraints( int uniqueId, const String8* path, const int action) = 0; + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path) = 0; + virtual bool canHandle(int uniqueId, const String8& path, const String8& mimeType) = 0; virtual DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo) = 0; @@ -179,6 +183,8 @@ public: virtual DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path); + virtual bool canHandle(int uniqueId, const String8& path, const String8& mimeType); virtual DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); diff --git a/drm/libdrmframework/plugins/common/include/DrmEngineBase.h b/drm/libdrmframework/plugins/common/include/DrmEngineBase.h index 5851af5..67b6355 100644 --- a/drm/libdrmframework/plugins/common/include/DrmEngineBase.h +++ b/drm/libdrmframework/plugins/common/include/DrmEngineBase.h @@ -36,6 +36,8 @@ public: public: DrmConstraints* getConstraints(int uniqueId, const String8* path, int action); + DrmMetadata* getMetadata(int uniqueId, const String8* path); + status_t initialize(int uniqueId); status_t setOnInfoListener(int uniqueId, const IDrmEngine::OnInfoListener* infoListener); @@ -117,6 +119,18 @@ protected: int uniqueId, const String8* path, int action) = 0; /** + * Get metadata information associated with input content + * + * @param[in] uniqueId Unique identifier for a session + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata + * @note + * In case of error, return NULL + */ + virtual DrmMetadata* onGetMetadata(int uniqueId, const String8* path) = 0; + + /** * Initialize plug-in * * @param[in] uniqueId Unique identifier for a session diff --git a/drm/libdrmframework/plugins/common/include/IDrmEngine.h b/drm/libdrmframework/plugins/common/include/IDrmEngine.h index cc03ef2..f839070 100644 --- a/drm/libdrmframework/plugins/common/include/IDrmEngine.h +++ b/drm/libdrmframework/plugins/common/include/IDrmEngine.h @@ -23,6 +23,7 @@ namespace android { class DrmContentIds; class DrmConstraints; +class DrmMetadata; class DrmRights; class DrmInfo; class DrmInfoStatus; @@ -105,6 +106,18 @@ public: int uniqueId, const String8* path, int action) = 0; /** + * Get metadata information associated with input content + * + * @param[in] uniqueId Unique identifier for a session + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata + * @note + * In case of error, return NULL + */ + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path) = 0; + + /** * Get whether the given content can be handled by this plugin or not * * @param[in] uniqueId Unique identifier for a session diff --git a/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h b/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h index ddb7fd3..bbcd9ed 100644 --- a/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h +++ b/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h @@ -30,6 +30,8 @@ public: protected: DrmConstraints* onGetConstraints(int uniqueId, const String8* path, int action); + DrmMetadata* onGetMetadata(int uniqueId, const String8* path); + status_t onInitialize(int uniqueId); status_t onSetOnInfoListener(int uniqueId, const IDrmEngine::OnInfoListener* infoListener); diff --git a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp index 41f8e91..dee1fdb 100644 --- a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp +++ b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp @@ -20,6 +20,7 @@ #include <drm/DrmRights.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmInfo.h> #include <drm/DrmInfoEvent.h> #include <drm/DrmInfoStatus.h> @@ -51,6 +52,10 @@ DrmPassthruPlugIn::~DrmPassthruPlugIn() { } +DrmMetadata* DrmPassthruPlugIn::onGetMetadata(int uniqueId, const String8* path) { + return NULL; +} + DrmConstraints* DrmPassthruPlugIn::onGetConstraints( int uniqueId, const String8* path, int action) { LOGD("DrmPassthruPlugIn::onGetConstraints From Path: %d", uniqueId); diff --git a/include/drm/DrmManagerClient.h b/include/drm/DrmManagerClient.h index 5963c42..3dbfbe2 100644 --- a/include/drm/DrmManagerClient.h +++ b/include/drm/DrmManagerClient.h @@ -25,6 +25,7 @@ namespace android { class DrmInfo; class DrmRights; +class DrmMetadata; class DrmInfoEvent; class DrmInfoStatus; class DrmInfoRequest; @@ -204,6 +205,17 @@ public: DrmConstraints* getConstraints(const String8* path, const int action); /** + * Get metadata information associated with input content + * + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata + * @note + * In case of error, return NULL + */ + DrmMetadata* getMetadata(const String8* path); + + /** * Check whether the given mimetype or path can be handled * * @param[in] path Path of the content needs to be handled diff --git a/include/drm/DrmMetadata.h b/include/drm/DrmMetadata.h new file mode 100644 index 0000000..2c7538a --- /dev/null +++ b/include/drm/DrmMetadata.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef __DRM_METADATA_H__ +#define __DRM_METADATA_H__ + +#include "drm_framework_common.h" + +namespace android { + +/** + * This is an utility class which contains the constraints information. + * + * As a result of DrmManagerClient::getMetadata(const String8*) + * an instance of DrmMetadata would be returned. + */ +class DrmMetadata { +public: + /** + * Iterator for key + */ + class KeyIterator { + friend class DrmMetadata; + private: + KeyIterator(DrmMetadata* drmMetadata) : mDrmMetadata(drmMetadata), mIndex(0) {} + + public: + KeyIterator(const KeyIterator& keyIterator); + KeyIterator& operator=(const KeyIterator& keyIterator); + virtual ~KeyIterator() {} + + public: + bool hasNext(); + const String8& next(); + + private: + DrmMetadata* mDrmMetadata; + unsigned int mIndex; + }; + + /** + * Iterator for constraints + */ + class Iterator { + friend class DrmMetadata; + private: + Iterator(DrmMetadata* drmMetadata) : mDrmMetadata(drmMetadata), mIndex(0) {} + + public: + Iterator(const Iterator& iterator); + Iterator& operator=(const Iterator& iterator); + virtual ~Iterator() {} + + public: + bool hasNext(); + String8 next(); + + private: + DrmMetadata* mDrmMetadata; + unsigned int mIndex; + }; + +public: + DrmMetadata() {} + virtual ~DrmMetadata() { + DrmMetadata::KeyIterator keyIt = this->keyIterator(); + + while (keyIt.hasNext()) { + String8 key = keyIt.next(); + const char* value = this->getAsByteArray(&key); + if (NULL != value) { + delete[] value; + value = NULL; + } + } + mMetadataMap.clear(); + } + +public: + int getCount(void) const; + status_t put(const String8* key, const char* value); + String8 get(const String8& key) const; + const char* getAsByteArray(const String8* key) const; + KeyIterator keyIterator(); + Iterator iterator(); + +private: + const char* getValue(const String8* key) const; + +private: + typedef KeyedVector<String8, const char*> DrmMetadataMap; + DrmMetadataMap mMetadataMap; +}; + +}; + +#endif /* __DRM_METADATA_H__ */ + diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 9789e36..781e3fc 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -526,6 +526,13 @@ status_t AwesomePlayer::play_l() { bool deferredAudioSeek = false; + if (mDecryptHandle != NULL) { + int64_t position; + getPosition(&position); + mDrmManagerClient->setPlaybackStatus(mDecryptHandle, + Playback::START, position / 1000); + } + if (mAudioSource != NULL) { if (mAudioPlayer == NULL) { if (mAudioSink != NULL) { @@ -543,6 +550,11 @@ status_t AwesomePlayer::play_l() { mFlags &= ~(PLAYING | FIRST_FRAME); + if (mDecryptHandle != NULL) { + mDrmManagerClient->setPlaybackStatus(mDecryptHandle, + Playback::STOP, 0); + } + return err; } @@ -582,13 +594,6 @@ status_t AwesomePlayer::play_l() { seekTo_l(0); } - if (mDecryptHandle != NULL) { - int64_t position; - getPosition(&position); - mDrmManagerClient->setPlaybackStatus(mDecryptHandle, - Playback::START, position / 1000); - } - return OK; } diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java new file mode 100644 index 0000000..3543275 --- /dev/null +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2010 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 com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.gsm.SmsCbHeader; + +import java.io.UnsupportedEncodingException; + +/** + * Describes an SMS-CB message. + * + * {@hide} + */ +public class SmsCbMessage { + + /** + * Cell wide immediate geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; + + /** + * PLMN wide geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; + + /** + * Location / service area wide geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2; + + /** + * Cell wide geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; + + /** + * Create an instance of this class from a received PDU + * + * @param pdu PDU bytes + * @return An instance of this class, or null if invalid pdu + */ + public static SmsCbMessage createFromPdu(byte[] pdu) { + try { + return new SmsCbMessage(pdu); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_0 = { + "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu", + "pl", null + }; + + /** + * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_2 = { + "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null, + null, null + }; + + private static final char CARRIAGE_RETURN = 0x0d; + + private SmsCbHeader mHeader; + + private String mLanguage; + + private String mBody; + + private SmsCbMessage(byte[] pdu) throws IllegalArgumentException { + mHeader = new SmsCbHeader(pdu); + parseBody(pdu); + } + + /** + * Return the geographical scope of this message, one of + * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE}, + * {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE}, + * {@link #GEOGRAPHICAL_SCOPE_LA_WIDE}, + * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE} + * + * @return Geographical scope + */ + public int getGeographicalScope() { + return mHeader.geographicalScope; + } + + /** + * Get the ISO-639-1 language code for this message, or null if unspecified + * + * @return Language code + */ + public String getLanguageCode() { + return mLanguage; + } + + /** + * Get the body of this message, or null if no body available + * + * @return Body, or null + */ + public String getMessageBody() { + return mBody; + } + + /** + * Get the message identifier of this message (0-65535) + * + * @return Message identifier + */ + public int getMessageIdentifier() { + return mHeader.messageIdentifier; + } + + /** + * Get the message code of this message (0-1023) + * + * @return Message code + */ + public int getMessageCode() { + return mHeader.messageCode; + } + + /** + * Get the update number of this message (0-15) + * + * @return Update number + */ + public int getUpdateNumber() { + return mHeader.updateNumber; + } + + private void parseBody(byte[] pdu) { + int encoding; + boolean hasLanguageIndicator = false; + + // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, + // section 5. + switch ((mHeader.dataCodingScheme & 0xf0) >> 4) { + case 0x00: + encoding = SmsMessage.ENCODING_7BIT; + mLanguage = LANGUAGE_CODES_GROUP_0[mHeader.dataCodingScheme & 0x0f]; + break; + + case 0x01: + hasLanguageIndicator = true; + if ((mHeader.dataCodingScheme & 0x0f) == 0x01) { + encoding = SmsMessage.ENCODING_16BIT; + } else { + encoding = SmsMessage.ENCODING_7BIT; + } + break; + + case 0x02: + encoding = SmsMessage.ENCODING_7BIT; + mLanguage = LANGUAGE_CODES_GROUP_2[mHeader.dataCodingScheme & 0x0f]; + break; + + case 0x03: + encoding = SmsMessage.ENCODING_7BIT; + break; + + case 0x04: + case 0x05: + switch ((mHeader.dataCodingScheme & 0x0c) >> 2) { + case 0x01: + encoding = SmsMessage.ENCODING_8BIT; + break; + + case 0x02: + encoding = SmsMessage.ENCODING_16BIT; + break; + + case 0x00: + default: + encoding = SmsMessage.ENCODING_7BIT; + break; + } + break; + + case 0x06: + case 0x07: + // Compression not supported + case 0x09: + // UDH structure not supported + case 0x0e: + // Defined by the WAP forum not supported + encoding = SmsMessage.ENCODING_UNKNOWN; + break; + + case 0x0f: + if (((mHeader.dataCodingScheme & 0x04) >> 2) == 0x01) { + encoding = SmsMessage.ENCODING_8BIT; + } else { + encoding = SmsMessage.ENCODING_7BIT; + } + break; + + default: + // Reserved values are to be treated as 7-bit + encoding = SmsMessage.ENCODING_7BIT; + break; + } + + switch (encoding) { + case SmsMessage.ENCODING_7BIT: + mBody = GsmAlphabet.gsm7BitPackedToString(pdu, SmsCbHeader.PDU_HEADER_LENGTH, + (pdu.length - SmsCbHeader.PDU_HEADER_LENGTH) * 8 / 7); + + if (hasLanguageIndicator && mBody != null && mBody.length() > 2) { + mLanguage = mBody.substring(0, 2); + mBody = mBody.substring(3); + } + break; + + case SmsMessage.ENCODING_16BIT: + int offset = SmsCbHeader.PDU_HEADER_LENGTH; + + if (hasLanguageIndicator && pdu.length >= SmsCbHeader.PDU_HEADER_LENGTH + 2) { + mLanguage = GsmAlphabet.gsm7BitPackedToString(pdu, + SmsCbHeader.PDU_HEADER_LENGTH, 2); + offset += 2; + } + + try { + mBody = new String(pdu, offset, (pdu.length & 0xfffe) - offset, "utf-16"); + } catch (UnsupportedEncodingException e) { + // Eeeek + } + break; + + default: + break; + } + + if (mBody != null) { + // Remove trailing carriage return + for (int i = mBody.length() - 1; i >= 0; i--) { + if (mBody.charAt(i) != CARRIAGE_RETURN) { + mBody = mBody.substring(0, i + 1); + break; + } + } + } else { + mBody = ""; + } + } +} diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index f5e9751..0ecd854 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -341,7 +341,67 @@ public final class SmsManager { } return createMessageListFromRawRecords(records); - } + } + + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. All received messages will be broadcast in an + * intent with the action "android.provider.telephony.SMS_CB_RECEIVED". + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * @see #disableCellBroadcast(int) + * + * {@hide} + */ + public boolean enableCellBroadcast(int messageIdentifier) { + boolean success = false; + + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + success = iccISms.enableCellBroadcast(messageIdentifier); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #enableCellBroadcast(int) + * + * {@hide} + */ + public boolean disableCellBroadcast(int messageIdentifier) { + boolean success = false; + + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + success = iccISms.disableCellBroadcast(messageIdentifier); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } /** * Create a list of <code>SmsMessage</code>s from a list of RawSmsData diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl index 65bad96..90de5e1 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -144,4 +144,30 @@ interface ISms { in List<String> parts, in List<PendingIntent> sentIntents, in List<PendingIntent> deliveryIntents); + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #disableCellBroadcast(int) + */ + boolean enableCellBroadcast(int messageIdentifier); + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #enableCellBroadcast(int) + */ + boolean disableCellBroadcast(int messageIdentifier); + } diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java index 1910a9c..5049249 100644 --- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java +++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java @@ -68,4 +68,12 @@ public class IccSmsInterfaceManagerProxy extends ISms.Stub { parts, sentIntents, deliveryIntents); } + public boolean enableCellBroadcast(int messageIdentifier) throws android.os.RemoteException { + return mIccSmsInterfaceManager.enableCellBroadcast(messageIdentifier); + } + + public boolean disableCellBroadcast(int messageIdentifier) throws android.os.RemoteException { + return mIccSmsInterfaceManager.disableCellBroadcast(messageIdentifier); + } + } diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index ca526a5..55e8450 100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -116,6 +116,9 @@ public abstract class SMSDispatcher extends Handler { /** Radio is ON */ static final protected int EVENT_RADIO_ON = 12; + /** New broadcast SMS */ + static final protected int EVENT_NEW_BROADCAST_SMS = 13; + protected Phone mPhone; protected Context mContext; protected ContentResolver mResolver; @@ -399,6 +402,9 @@ public abstract class SMSDispatcher extends Handler { mCm.reportSmsMemoryStatus(mStorageAvailable, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); } + + case EVENT_NEW_BROADCAST_SMS: + handleBroadcastSms((AsyncResult)msg.obj); break; } } @@ -995,4 +1001,17 @@ public abstract class SMSDispatcher extends Handler { } }; + + protected abstract void handleBroadcastSms(AsyncResult ar); + + protected void dispatchBroadcastPdus(byte[][] pdus) { + Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED"); + intent.putExtra("pdus", pdus); + + if (Config.LOGD) + Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); + + dispatch(intent, "android.permission.RECEIVE_SMS"); + } + } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index ed93aea..8b2ea9b 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -488,6 +488,11 @@ final class CdmaSMSDispatcher extends SMSDispatcher { mCm.setCdmaBroadcastConfig(configValuesArray, response); } + protected void handleBroadcastSms(AsyncResult ar) { + // Not supported + Log.e(TAG, "Error! Not implemented for CDMA."); + } + private int resultToCause(int rc) { switch (rc) { case Activity.RESULT_OK: diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java index cfcfd98..422c1e2 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java @@ -191,6 +191,18 @@ public class RuimSmsInterfaceManager extends IccSmsInterfaceManager { return mSms; } + public boolean enableCellBroadcast(int messageIdentifier) { + // Not implemented + Log.e(LOG_TAG, "Error! Not implemented for CDMA."); + return false; + } + + public boolean disableCellBroadcast(int messageIdentifier) { + // Not implemented + Log.e(LOG_TAG, "Error! Not implemented for CDMA."); + return false; + } + protected void log(String msg) { Log.d(LOG_TAG, "[RuimSmsInterfaceManager] " + msg); } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index d720516..438c811 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -22,21 +22,27 @@ import android.app.PendingIntent.CanceledException; import android.content.Intent; import android.os.AsyncResult; import android.os.Message; +import android.os.SystemProperties; import android.provider.Telephony.Sms.Intents; import android.telephony.ServiceState; +import android.telephony.SmsCbMessage; +import android.telephony.gsm.GsmCellLocation; import android.util.Config; import android.util.Log; import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; import com.android.internal.telephony.gsm.SmsMessage; +import com.android.internal.telephony.BaseCommands; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.TelephonyProperties; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import static android.telephony.SmsMessage.MessageClass; @@ -48,6 +54,8 @@ final class GsmSMSDispatcher extends SMSDispatcher { GsmSMSDispatcher(GSMPhone phone) { super(phone); mGsmPhone = phone; + + ((BaseCommands)mCm).setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null); } /** @@ -384,4 +392,162 @@ final class GsmSMSDispatcher extends SMSDispatcher { return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR; } } + + /** + * Holds all info about a message page needed to assemble a complete + * concatenated message + */ + private static final class SmsCbConcatInfo { + private final SmsCbHeader mHeader; + + private final String mPlmn; + + private final int mLac; + + private final int mCid; + + public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) { + mHeader = header; + mPlmn = plmn; + mLac = lac; + mCid = cid; + } + + @Override + public int hashCode() { + return mHeader.messageIdentifier * 31 + mHeader.updateNumber; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SmsCbConcatInfo) { + SmsCbConcatInfo other = (SmsCbConcatInfo)obj; + + // Two pages match if all header attributes (except the page + // index) are identical, and both pages belong to the same + // location (which is also determined by the scope parameter) + if (mHeader.geographicalScope == other.mHeader.geographicalScope + && mHeader.messageCode == other.mHeader.messageCode + && mHeader.updateNumber == other.mHeader.updateNumber + && mHeader.messageIdentifier == other.mHeader.messageIdentifier + && mHeader.dataCodingScheme == other.mHeader.dataCodingScheme + && mHeader.nrOfPages == other.mHeader.nrOfPages) { + return matchesLocation(other.mPlmn, other.mLac, other.mCid); + } + } + + return false; + } + + /** + * Checks if this concatenation info matches the given location. The + * granularity of the match depends on the geographical scope. + * + * @param plmn PLMN + * @param lac Location area code + * @param cid Cell ID + * @return true if matching, false otherwise + */ + public boolean matchesLocation(String plmn, int lac, int cid) { + switch (mHeader.geographicalScope) { + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: + if (mCid != cid) { + return false; + } + // deliberate fall-through + case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: + if (mLac != lac) { + return false; + } + // deliberate fall-through + case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: + return mPlmn != null && mPlmn.equals(plmn); + } + + return false; + } + } + + // This map holds incomplete concatenated messages waiting for assembly + private HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = + new HashMap<SmsCbConcatInfo, byte[][]>(); + + protected void handleBroadcastSms(AsyncResult ar) { + try { + byte[][] pdus = null; + byte[] receivedPdu = (byte[])ar.result; + + if (Config.LOGD) { + for (int i = 0; i < receivedPdu.length; i += 8) { + StringBuilder sb = new StringBuilder("SMS CB pdu data: "); + for (int j = i; j < i + 8 && j < receivedPdu.length; j++) { + int b = receivedPdu[j] & 0xff; + if (b < 0x10) { + sb.append("0"); + } + sb.append(Integer.toHexString(b)).append(" "); + } + Log.d(TAG, sb.toString()); + } + } + + SmsCbHeader header = new SmsCbHeader(receivedPdu); + String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); + GsmCellLocation cellLocation = (GsmCellLocation)mGsmPhone.getCellLocation(); + int lac = cellLocation.getLac(); + int cid = cellLocation.getCid(); + + if (header.nrOfPages > 1) { + // Multi-page message + SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid); + + // Try to find other pages of the same message + pdus = mSmsCbPageMap.get(concatInfo); + + if (pdus == null) { + // This it the first page of this message, make room for all + // pages and keep until complete + pdus = new byte[header.nrOfPages][]; + + mSmsCbPageMap.put(concatInfo, pdus); + } + + // Page parameter is one-based + pdus[header.pageIndex - 1] = receivedPdu; + + for (int i = 0; i < pdus.length; i++) { + if (pdus[i] == null) { + // Still missing pages, exit + return; + } + } + + // Message complete, remove and dispatch + mSmsCbPageMap.remove(concatInfo); + } else { + // Single page message + pdus = new byte[1][]; + pdus[0] = receivedPdu; + } + + dispatchBroadcastPdus(pdus); + + // Remove messages that are out of scope to prevent the map from + // growing indefinitely, containing incomplete messages that were + // never assembled + Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator(); + + while (iter.hasNext()) { + SmsCbConcatInfo info = iter.next(); + + if (!info.matchesLocation(plmn, lac, cid)) { + iter.remove(); + } + } + } catch (RuntimeException e) { + Log.e(TAG, "Error in decoding SMS CB pdu", e); + } + } + } diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java index 2028ca4..a5e8378 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java @@ -17,7 +17,9 @@ package com.android.internal.telephony.gsm; import android.content.Context; +import android.content.pm.PackageManager; import android.os.AsyncResult; +import android.os.Binder; import android.os.Handler; import android.os.Message; import android.util.Log; @@ -29,7 +31,10 @@ import com.android.internal.telephony.SmsRawData; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static android.telephony.SmsManager.STATUS_ON_ICC_FREE; @@ -44,9 +49,15 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { private final Object mLock = new Object(); private boolean mSuccess; private List<SmsRawData> mSms; + private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions = + new HashMap<Integer, HashSet<String>>(); private static final int EVENT_LOAD_DONE = 1; private static final int EVENT_UPDATE_DONE = 2; + private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3; + private static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4; + private static final int SMS_CB_CODE_SCHEME_MIN = 0; + private static final int SMS_CB_CODE_SCHEME_MAX = 255; Handler mHandler = new Handler() { @Override @@ -74,6 +85,14 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { mLock.notifyAll(); } break; + case EVENT_SET_BROADCAST_ACTIVATION_DONE: + case EVENT_SET_BROADCAST_CONFIG_DONE: + ar = (AsyncResult) msg.obj; + synchronized (mLock) { + mSuccess = (ar.exception == null); + mLock.notifyAll(); + } + break; } } }; @@ -191,6 +210,126 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { return mSms; } + public boolean enableCellBroadcast(int messageIdentifier) { + if (DBG) log("enableCellBroadcast"); + + Context context = mPhone.getContext(); + + context.enforceCallingPermission( + "android.permission.RECEIVE_SMS", + "Enabling cell broadcast SMS"); + + String client = context.getPackageManager().getNameForUid( + Binder.getCallingUid()); + HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier); + + if (clients == null) { + // This is a new message identifier + clients = new HashSet<String>(); + mCellBroadcastSubscriptions.put(messageIdentifier, clients); + + if (!updateCellBroadcastConfig()) { + mCellBroadcastSubscriptions.remove(messageIdentifier); + return false; + } + } + + clients.add(client); + + if (DBG) + log("Added cell broadcast subscription for MID " + messageIdentifier + + " from client " + client); + + return true; + } + + public boolean disableCellBroadcast(int messageIdentifier) { + if (DBG) log("disableCellBroadcast"); + + Context context = mPhone.getContext(); + + context.enforceCallingPermission( + "android.permission.RECEIVE_SMS", + "Disabling cell broadcast SMS"); + + String client = context.getPackageManager().getNameForUid( + Binder.getCallingUid()); + HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier); + + if (clients != null && clients.remove(client)) { + if (DBG) + log("Removed cell broadcast subscription for MID " + messageIdentifier + + " from client " + client); + + if (clients.isEmpty()) { + mCellBroadcastSubscriptions.remove(messageIdentifier); + updateCellBroadcastConfig(); + } + return true; + } + + return false; + } + + private boolean updateCellBroadcastConfig() { + Set<Integer> messageIdentifiers = mCellBroadcastSubscriptions.keySet(); + + if (messageIdentifiers.size() > 0) { + SmsBroadcastConfigInfo[] configs = + new SmsBroadcastConfigInfo[messageIdentifiers.size()]; + int i = 0; + + for (int messageIdentifier : messageIdentifiers) { + configs[i++] = new SmsBroadcastConfigInfo(messageIdentifier, messageIdentifier, + SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, true); + } + + return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true); + } else { + return setCellBroadcastActivation(false); + } + } + + private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) { + if (DBG) + log("Calling setGsmBroadcastConfig with " + configs.length + " configurations"); + + synchronized (mLock) { + Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE); + + mSuccess = false; + mPhone.mCM.setGsmBroadcastConfig(configs, response); + + try { + mLock.wait(); + } catch (InterruptedException e) { + log("interrupted while trying to set cell broadcast config"); + } + } + + return mSuccess; + } + + private boolean setCellBroadcastActivation(boolean activate) { + if (DBG) + log("Calling setCellBroadcastActivation(" + activate + ")"); + + synchronized (mLock) { + Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE); + + mSuccess = false; + mPhone.mCM.setGsmBroadcastActivation(activate, response); + + try { + mLock.wait(); + } catch (InterruptedException e) { + log("interrupted while trying to set cell broadcast activation"); + } + } + + return mSuccess; + } + protected void log(String msg) { Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg); } diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java new file mode 100644 index 0000000..5f27cfc --- /dev/null +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 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.gsm; + +public class SmsCbHeader { + public static final int PDU_HEADER_LENGTH = 6; + + public final int geographicalScope; + + public final int messageCode; + + public final int updateNumber; + + public final int messageIdentifier; + + public final int dataCodingScheme; + + public final int pageIndex; + + public final int nrOfPages; + + public SmsCbHeader(byte[] pdu) throws IllegalArgumentException { + if (pdu == null || pdu.length < PDU_HEADER_LENGTH) { + throw new IllegalArgumentException("Illegal PDU"); + } + + geographicalScope = (pdu[0] & 0xc0) >> 6; + messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4); + updateNumber = pdu[1] & 0x0f; + messageIdentifier = (pdu[2] << 8) | pdu[3]; + dataCodingScheme = pdu[4]; + + // Check for invalid page parameter + int pageIndex = (pdu[5] & 0xf0) >> 4; + int nrOfPages = pdu[5] & 0x0f; + + if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) { + pageIndex = 1; + nrOfPages = 1; + } + + this.pageIndex = pageIndex; + this.nrOfPages = nrOfPages; + } +} diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java new file mode 100644 index 0000000..7136ea0 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2010 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.telephony.SmsCbMessage; +import android.test.AndroidTestCase; + +/** + * Test cases for basic SmsCbMessage operations + */ +public class GsmSmsCbTest extends AndroidTestCase { + + private void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) { + pdu[0] = b; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected geographical scope decoded", expectedGs, msg + .getGeographicalScope()); + } + + public void testCreateNullPdu() { + SmsCbMessage msg = SmsCbMessage.createFromPdu(null); + + assertNull("createFromPdu(byte[] with null pdu should return null", msg); + } + + public void testCreateTooShortPdu() { + byte[] pdu = new byte[4]; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertNull("createFromPdu(byte[] with too short pdu should return null", msg); + } + + public void testGetGeographicalScope() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + doTestGeographicalScopeValue(pdu, (byte)0x00, + SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE); + doTestGeographicalScopeValue(pdu, (byte)0x40, SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE); + doTestGeographicalScopeValue(pdu, (byte)0x80, SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE); + doTestGeographicalScopeValue(pdu, (byte)0xC0, SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE); + } + + public void testGetMessageBody7Bit() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A GSM default alphabet message with carriage return padding", + msg.getMessageBody()); + } + + public void testGetMessageBody7BitFull() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5, + (byte)0xB4, (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63, + (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40, (byte)0x63, + (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3, (byte)0xCB, (byte)0xF2, + (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76, (byte)0x9F, (byte)0x59, (byte)0xA0, + (byte)0x76, (byte)0x39, (byte)0xEC, (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20, + (byte)0x3A, (byte)0xBA, (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73, + (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4, + (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals( + "Unexpected 7-bit string decoded", + "A GSM default alphabet message being exactly 93 characters long, " + + "meaning there is no padding!", + msg.getMessageBody()); + } + + public void testGetMessageBody7BitWithLanguage() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x04, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A GSM default alphabet message with carriage return padding", + msg.getMessageBody()); + + assertEquals("Unexpected language indicator decoded", "es", msg.getLanguageCode()); + } + + public void testGetMessageBody7BitWithLanguageInBody() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x10, (byte)0x11, (byte)0x73, + (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E, (byte)0x9B, (byte)0x20, + (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE, (byte)0xB3, (byte)0xE9, (byte)0xA0, + (byte)0x30, (byte)0x1B, (byte)0x8E, (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74, + (byte)0x50, (byte)0xBB, (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65, + (byte)0xD0, (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61, + (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41, (byte)0xF2, + (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83, (byte)0xE0, (byte)0x61, + (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E, (byte)0x37, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A GSM default alphabet message with carriage return padding", + msg.getMessageBody()); + + assertEquals("Unexpected language indicator decoded", "sv", msg.getLanguageCode()); + } + + public void testGetMessageBody8Bit() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x44, (byte)0x11, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("8-bit message body should be empty", "", msg.getMessageBody()); + } + + public void testGetMessageBodyUcs2() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x48, (byte)0x11, (byte)0x00, + (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43, + (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00, + (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73, + (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00, + (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E, + (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69, (byte)0x00, + (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67, + (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04, + (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68, + (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00, + (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72, + (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A UCS2 message containing a \u0434 character", msg.getMessageBody()); + } + + public void testGetMessageBodyUcs2WithLanguageInBody() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x11, (byte)0x11, (byte)0x78, + (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, + (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, + (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, + (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, + (byte)0x65, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, + (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, + (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, + (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, + (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, + (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, + (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, + (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A UCS2 message containing a \u0434 character", msg.getMessageBody()); + + assertEquals("Unexpected language indicator decoded", "xx", msg.getLanguageCode()); + } + + public void testGetMessageIdentifier() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier()); + } + + public void testGetMessageCode() { + byte[] pdu = { + (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected message code decoded", 682, msg.getMessageCode()); + } + + public void testGetUpdateNumber() { + byte[] pdu = { + (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); + } +} |