Merge "Use the advertised profile and level from M4V and H263 video encoders" into gingerbread
diff --git a/api/current.xml b/api/current.xml
index de26a2b..b141475 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -62273,6 +62273,146 @@
>
</field>
</class>
+<class name="BitmapRegionDecoder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="decodeRegion"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rect" type="android.graphics.Rect">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
+<method name="getHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getWidth"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isRecycled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="byte[]">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="is" type="java.io.InputStream">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pathName" type="java.lang.String">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="recycle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
<class name="BitmapShader"
extends="android.graphics.Shader"
abstract="false"
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3876a3e..156da47 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -232,6 +232,8 @@
/**
* Sets whether this WakeLock is ref counted.
*
+ * <p>Wake locks are reference counted by default.
+ *
* @param value true for ref counted, false for not ref counted.
*/
public void setReferenceCounted(boolean value)
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index a038cc5..8c280b40 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -101,11 +101,12 @@
android_graphics_PixelFormat.cpp \
android/graphics/Picture.cpp \
android/graphics/PorterDuff.cpp \
- android/graphics/LargeBitmap.cpp \
+ android/graphics/BitmapRegionDecoder.cpp \
android/graphics/Rasterizer.cpp \
android/graphics/Region.cpp \
android/graphics/Shader.cpp \
android/graphics/Typeface.cpp \
+ android/graphics/Utils.cpp \
android/graphics/Xfermode.cpp \
android/graphics/YuvToJpegEncoder.cpp \
android_media_AudioRecord.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 17ab46c..beb49c8 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -53,7 +53,7 @@
extern int register_android_os_Process(JNIEnv* env);
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
-extern int register_android_graphics_LargeBitmap(JNIEnv*);
+extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_Interpolator(JNIEnv* env);
@@ -1208,7 +1208,7 @@
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
- REG_JNI(register_android_graphics_LargeBitmap),
+ REG_JNI(register_android_graphics_BitmapRegionDecoder),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_Canvas),
REG_JNI(register_android_graphics_ColorFilter),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 6745b24..90a0243 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -10,6 +10,7 @@
#include "SkUtils.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "AutoDecodeCancel.h"
+#include "Utils.h"
#include <android_runtime/AndroidRuntime.h>
#include <utils/Asset.h>
@@ -99,57 +100,6 @@
}
};
-class AssetStreamAdaptor : public SkStream {
-public:
- AssetStreamAdaptor(Asset* a) : fAsset(a) {}
-
- virtual bool rewind() {
- off_t pos = fAsset->seek(0, SEEK_SET);
- if (pos == (off_t)-1) {
- SkDebugf("----- fAsset->seek(rewind) failed\n");
- return false;
- }
- return true;
- }
-
- virtual size_t read(void* buffer, size_t size) {
- ssize_t amount;
-
- if (NULL == buffer) {
- if (0 == size) { // caller is asking us for our total length
- return fAsset->getLength();
- }
- // asset->seek returns new total offset
- // we want to return amount that was skipped
-
- off_t oldOffset = fAsset->seek(0, SEEK_CUR);
- if (-1 == oldOffset) {
- SkDebugf("---- fAsset->seek(oldOffset) failed\n");
- return 0;
- }
- off_t newOffset = fAsset->seek(size, SEEK_CUR);
- if (-1 == newOffset) {
- SkDebugf("---- fAsset->seek(%d) failed\n", size);
- return 0;
- }
- amount = newOffset - oldOffset;
- } else {
- amount = fAsset->read(buffer, size);
- if (amount <= 0) {
- SkDebugf("---- fAsset->read(%d) returned %d\n", size, amount);
- }
- }
-
- if (amount < 0) {
- amount = 0;
- }
- return amount;
- }
-
-private:
- Asset* fAsset;
-};
-
///////////////////////////////////////////////////////////////////////////////
static inline int32_t validOrNeg1(bool isValid, int32_t value) {
@@ -201,12 +151,6 @@
!env->GetBooleanField(options, gOptions_nativeAllocFieldID);
}
-static jobject nullObjectReturn(const char msg[]) {
- if (msg) {
- SkDebugf("--- %s\n", msg);
- }
- return NULL;
-}
static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
int sampleSize, bool ditherImage) {
@@ -377,23 +321,6 @@
return size;
}
-/** Restore the file descriptor's offset in our destructor
- */
-class AutoFDSeek {
-public:
- AutoFDSeek(int fd) : fFD(fd) {
- fCurr = ::lseek(fd, 0, SEEK_CUR);
- }
- ~AutoFDSeek() {
- if (fCurr >= 0) {
- ::lseek(fFD, fCurr, SEEK_SET);
- }
- }
-private:
- int fFD;
- off_t fCurr;
-};
-
static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
jobject fileDescriptor,
jobject padding,
@@ -560,134 +487,6 @@
}
}
-static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
- size_t bufferSize = 4096;
- size_t streamLen = 0;
- size_t len;
- char* data = (char*)sk_malloc_throw(bufferSize);
-
- while ((len = stream->read(data + streamLen,
- bufferSize - streamLen)) != 0) {
- streamLen += len;
- if (streamLen == bufferSize) {
- bufferSize *= 2;
- data = (char*)sk_realloc_throw(data, bufferSize);
- }
- }
- data = (char*)sk_realloc_throw(data, streamLen);
- SkMemoryStream* streamMem = new SkMemoryStream();
- streamMem->setMemoryOwned(data, streamLen);
- return streamMem;
-}
-
-static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
- SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
- int width, height;
- if (NULL == decoder) {
- doThrowIOE(env, "Image format not supported");
- return nullObjectReturn("SkImageDecoder::Factory returned null");
- }
-
- JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true);
- decoder->setAllocator(javaAllocator);
- JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env);
- decoder->setReporter(javaMemoryReporter);
- javaAllocator->unref();
- javaMemoryReporter->unref();
-
- if (!decoder->buildTileIndex(stream, &width, &height)) {
- char msg[100];
- snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
- decoder->getFormatName());
- doThrowIOE(env, msg);
- return nullObjectReturn("decoder->buildTileIndex returned false");
- }
-
- SkLargeBitmap *bm = new SkLargeBitmap(decoder, width, height);
-
- return GraphicsJNI::createLargeBitmap(env, bm);
-}
-
-static jobject nativeCreateLargeBitmapFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
- int offset, int length, jboolean isShareable) {
- /* If isShareable we could decide to just wrap the java array and
- share it, but that means adding a globalref to the java array object
- For now we just always copy the array's data if isShareable.
- */
- AutoJavaByteArray ar(env, byteArray);
- SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
- return doBuildTileIndex(env, stream);
-}
-
-static jobject nativeCreateLargeBitmapFromFileDescriptor(JNIEnv* env, jobject clazz,
- jobject fileDescriptor, jboolean isShareable) {
- NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
-
- jint descriptor = env->GetIntField(fileDescriptor,
- gFileDescriptor_descriptor);
- SkStream *stream = NULL;
- struct stat fdStat;
- int newFD;
- if (fstat(descriptor, &fdStat) == -1) {
- doThrowIOE(env, "broken file descriptor");
- return nullObjectReturn("fstat return -1");
- }
-
- if (isShareable &&
- S_ISREG(fdStat.st_mode) &&
- (newFD = ::dup(descriptor)) != -1) {
- SkFDStream* fdStream = new SkFDStream(newFD, true);
- if (!fdStream->isValid()) {
- fdStream->unref();
- return NULL;
- }
- stream = fdStream;
- } else {
- SkFDStream* fdStream = new SkFDStream(descriptor, false);
- if (!fdStream->isValid()) {
- fdStream->unref();
- return NULL;
- }
- stream = buildSkMemoryStream(fdStream);
- fdStream->unref();
- }
-
- /* Restore our offset when we leave, so we can be called more than once
- with the same descriptor. This is only required if we didn't dup the
- file descriptor, but it is OK to do it all the time.
- */
- AutoFDSeek as(descriptor);
-
- return doBuildTileIndex(env, stream);
-}
-
-static jobject nativeCreateLargeBitmapFromStream(JNIEnv* env, jobject clazz,
- jobject is, // InputStream
- jbyteArray storage, // byte[]
- jboolean isShareable) {
- jobject largeBitmap = NULL;
- SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
-
- if (stream) {
- // for now we don't allow shareable with java inputstreams
- SkMemoryStream *mStream = buildSkMemoryStream(stream);
- largeBitmap = doBuildTileIndex(env, mStream);
- stream->unref();
- }
- return largeBitmap;
-}
-
-static jobject nativeCreateLargeBitmapFromAsset(JNIEnv* env, jobject clazz,
- jint native_asset, // Asset
- jboolean isShareable) {
- SkStream* stream, *assStream;
- Asset* asset = reinterpret_cast<Asset*>(native_asset);
- assStream = new AssetStreamAdaptor(asset);
- stream = buildSkMemoryStream(assStream);
- assStream->unref();
- return doBuildTileIndex(env, stream);
-}
-
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gMethods[] = {
@@ -717,26 +516,6 @@
},
{ "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
-
- { "nativeCreateLargeBitmap",
- "([BIIZ)Landroid/graphics/LargeBitmap;",
- (void*)nativeCreateLargeBitmapFromByteArray
- },
-
- { "nativeCreateLargeBitmap",
- "(Ljava/io/InputStream;[BZ)Landroid/graphics/LargeBitmap;",
- (void*)nativeCreateLargeBitmapFromStream
- },
-
- { "nativeCreateLargeBitmap",
- "(Ljava/io/FileDescriptor;Z)Landroid/graphics/LargeBitmap;",
- (void*)nativeCreateLargeBitmapFromFileDescriptor
- },
-
- { "nativeCreateLargeBitmap",
- "(IZ)Landroid/graphics/LargeBitmap;",
- (void*)nativeCreateLargeBitmapFromAsset
- },
};
static JNINativeMethod gOptionsMethods[] = {
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
new file mode 100644
index 0000000..4503852
--- /dev/null
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -0,0 +1,305 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BitmapRegionDecoder"
+
+#include "SkBitmap.h"
+#include "SkImageEncoder.h"
+#include "GraphicsJNI.h"
+#include "SkUtils.h"
+#include "SkTemplates.h"
+#include "SkPixelRef.h"
+#include "SkStream.h"
+#include "BitmapFactory.h"
+#include "AutoDecodeCancel.h"
+#include "SkBitmapRegionDecoder.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "Utils.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include "android_util_Binder.h"
+#include "android_nio_utils.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+#include <binder/Parcel.h>
+#include <jni.h>
+#include <utils/Asset.h>
+#include <sys/stat.h>
+
+static jclass gFileDescriptor_class;
+static jfieldID gFileDescriptor_descriptor;
+
+#if 0
+ #define TRACE_BITMAP(code) code
+#else
+ #define TRACE_BITMAP(code)
+#endif
+
+using namespace android;
+
+static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
+ size_t bufferSize = 4096;
+ size_t streamLen = 0;
+ size_t len;
+ char* data = (char*)sk_malloc_throw(bufferSize);
+
+ while ((len = stream->read(data + streamLen,
+ bufferSize - streamLen)) != 0) {
+ streamLen += len;
+ if (streamLen == bufferSize) {
+ bufferSize *= 2;
+ data = (char*)sk_realloc_throw(data, bufferSize);
+ }
+ }
+ data = (char*)sk_realloc_throw(data, streamLen);
+ SkMemoryStream* streamMem = new SkMemoryStream();
+ streamMem->setMemoryOwned(data, streamLen);
+ return streamMem;
+}
+
+static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
+ SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
+ int width, height;
+ if (NULL == decoder) {
+ doThrowIOE(env, "Image format not supported");
+ return nullObjectReturn("SkImageDecoder::Factory returned null");
+ }
+
+ JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true);
+ decoder->setAllocator(javaAllocator);
+ JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env);
+ decoder->setReporter(javaMemoryReporter);
+ javaAllocator->unref();
+ javaMemoryReporter->unref();
+
+ if (!decoder->buildTileIndex(stream, &width, &height)) {
+ char msg[100];
+ snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
+ decoder->getFormatName());
+ doThrowIOE(env, msg);
+ return nullObjectReturn("decoder->buildTileIndex returned false");
+ }
+
+ SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, width, height);
+
+ return GraphicsJNI::createBitmapRegionDecoder(env, bm);
+}
+
+static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
+ int offset, int length, jboolean isShareable) {
+ /* If isShareable we could decide to just wrap the java array and
+ share it, but that means adding a globalref to the java array object
+ For now we just always copy the array's data if isShareable.
+ */
+ AutoJavaByteArray ar(env, byteArray);
+ SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
+ return doBuildTileIndex(env, stream);
+}
+
+static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
+ jobject fileDescriptor, jboolean isShareable) {
+ NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
+
+ jint descriptor = env->GetIntField(fileDescriptor,
+ gFileDescriptor_descriptor);
+ SkStream *stream = NULL;
+ struct stat fdStat;
+ int newFD;
+ if (fstat(descriptor, &fdStat) == -1) {
+ doThrowIOE(env, "broken file descriptor");
+ return nullObjectReturn("fstat return -1");
+ }
+
+ if (isShareable &&
+ S_ISREG(fdStat.st_mode) &&
+ (newFD = ::dup(descriptor)) != -1) {
+ SkFDStream* fdStream = new SkFDStream(newFD, true);
+ if (!fdStream->isValid()) {
+ fdStream->unref();
+ return NULL;
+ }
+ stream = fdStream;
+ } else {
+ SkFDStream* fdStream = new SkFDStream(descriptor, false);
+ if (!fdStream->isValid()) {
+ fdStream->unref();
+ return NULL;
+ }
+ stream = buildSkMemoryStream(fdStream);
+ fdStream->unref();
+ }
+
+ /* Restore our offset when we leave, so we can be called more than once
+ with the same descriptor. This is only required if we didn't dup the
+ file descriptor, but it is OK to do it all the time.
+ */
+ AutoFDSeek as(descriptor);
+
+ return doBuildTileIndex(env, stream);
+}
+
+static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
+ jobject is, // InputStream
+ jbyteArray storage, // byte[]
+ jboolean isShareable) {
+ jobject largeBitmap = NULL;
+ SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
+
+ if (stream) {
+ // for now we don't allow shareable with java inputstreams
+ SkMemoryStream *mStream = buildSkMemoryStream(stream);
+ largeBitmap = doBuildTileIndex(env, mStream);
+ stream->unref();
+ }
+ return largeBitmap;
+}
+
+static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
+ jint native_asset, // Asset
+ jboolean isShareable) {
+ SkStream* stream, *assStream;
+ Asset* asset = reinterpret_cast<Asset*>(native_asset);
+ assStream = new AssetStreamAdaptor(asset);
+ stream = buildSkMemoryStream(assStream);
+ assStream->unref();
+ return doBuildTileIndex(env, stream);
+}
+
+/*
+ * nine patch not supported
+ *
+ * purgeable not supported
+ * reportSizeToVM not supported
+ */
+static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
+ int start_x, int start_y, int width, int height, jobject options) {
+ SkImageDecoder *decoder = brd->getDecoder();
+ int sampleSize = 1;
+ SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
+ bool doDither = true;
+
+ if (NULL != options) {
+ sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
+ // initialize these, in case we fail later on
+ env->SetIntField(options, gOptions_widthFieldID, -1);
+ env->SetIntField(options, gOptions_heightFieldID, -1);
+ env->SetObjectField(options, gOptions_mimeFieldID, 0);
+
+ jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
+ prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
+ doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
+ }
+
+ decoder->setDitherImage(doDither);
+ SkBitmap* bitmap = new SkBitmap;
+ SkAutoTDelete<SkBitmap> adb(bitmap);
+ AutoDecoderCancel adc(options, decoder);
+
+ // To fix the race condition in case "requestCancelDecode"
+ // happens earlier than AutoDecoderCancel object is added
+ // to the gAutoDecoderCancelMutex linked list.
+ if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
+ return nullObjectReturn("gOptions_mCancelID");;
+ }
+
+ SkIRect region;
+ region.fLeft = start_x;
+ region.fTop = start_y;
+ region.fRight = start_x + width;
+ region.fBottom = start_y + height;
+
+ if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
+ return nullObjectReturn("decoder->decodeRegion returned false");
+ }
+
+ // update options (if any)
+ if (NULL != options) {
+ env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
+ env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
+ // TODO: set the mimeType field with the data from the codec.
+ // but how to reuse a set of strings, rather than allocating new one
+ // each time?
+ env->SetObjectField(options, gOptions_mimeFieldID,
+ getMimeTypeString(env, decoder->getFormat()));
+ }
+
+ // detach bitmap from its autotdeleter, since we want to own it now
+ adb.detach();
+
+ SkPixelRef* pr;
+ pr = bitmap->pixelRef();
+ // promise we will never change our pixels (great for sharing and pictures)
+ pr->setImmutable();
+ // now create the java bitmap
+ return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
+}
+
+static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+ return brd->getHeight();
+}
+
+static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+ return brd->getWidth();
+}
+
+static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+ delete brd;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gBitmapRegionDecoderMethods[] = {
+ { "nativeDecodeRegion",
+ "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ (void*)nativeDecodeRegion},
+
+ { "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
+
+ { "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
+
+ { "nativeClean", "(I)V", (void*)nativeClean},
+
+ { "nativeNewInstance",
+ "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
+ (void*)nativeNewInstanceFromByteArray
+ },
+
+ { "nativeNewInstance",
+ "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
+ (void*)nativeNewInstanceFromStream
+ },
+
+ { "nativeNewInstance",
+ "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
+ (void*)nativeNewInstanceFromFileDescriptor
+ },
+
+ { "nativeNewInstance",
+ "(IZ)Landroid/graphics/BitmapRegionDecoder;",
+ (void*)nativeNewInstanceFromAsset
+ },
+};
+
+#define kClassPathName "android/graphics/BitmapRegionDecoder"
+
+int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
+int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
+{
+ return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
+ gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
+}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 72cea65..1ebe14b 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -168,8 +168,8 @@
static jclass gBitmapConfig_class;
static jfieldID gBitmapConfig_nativeInstanceID;
-static jclass gLargeBitmap_class;
-static jmethodID gLargeBitmap_constructorMethodID;
+static jclass gBitmapRegionDecoder_class;
+static jmethodID gBitmapRegionDecoder_constructorMethodID;
static jclass gCanvas_class;
static jfieldID gCanvas_nativeInstanceID;
@@ -376,17 +376,18 @@
}
return obj;
}
-jobject GraphicsJNI::createLargeBitmap(JNIEnv* env, SkLargeBitmap* bitmap)
+
+jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
{
SkASSERT(bitmap != NULL);
- jobject obj = env->AllocObject(gLargeBitmap_class);
+ jobject obj = env->AllocObject(gBitmapRegionDecoder_class);
if (hasException(env)) {
obj = NULL;
return obj;
}
if (obj) {
- env->CallVoidMethod(obj, gLargeBitmap_constructorMethodID, (jint)bitmap);
+ env->CallVoidMethod(obj, gBitmapRegionDecoder_constructorMethodID, (jint)bitmap);
if (hasException(env)) {
obj = NULL;
}
@@ -612,8 +613,8 @@
gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
"(IZ[BI)V");
- gLargeBitmap_class = make_globalref(env, "android/graphics/LargeBitmap");
- gLargeBitmap_constructorMethodID = env->GetMethodID(gLargeBitmap_class, "<init>", "(I)V");
+ gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
+ gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V");
gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config");
gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class,
@@ -651,4 +652,3 @@
return 0;
}
-
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 1a43a3e..d0f9125 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -4,7 +4,7 @@
#include "SkPoint.h"
#include "SkRect.h"
#include "SkBitmap.h"
-#include "../images/SkLargeBitmap.h"
+#include "../images/SkBitmapRegionDecoder.h"
#include "../images/SkImageDecoder.h"
#include <jni.h>
@@ -56,7 +56,7 @@
static jobject createRegion(JNIEnv* env, SkRegion* region);
- static jobject createLargeBitmap(JNIEnv* env, SkLargeBitmap* bitmap);
+ static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
/** Set a pixelref for the bitmap (needs setConfig to already be called)
Returns true on success. If it returns false, then it failed, and the
@@ -181,4 +181,3 @@
do { if (NULL == (object)) { doThrowNPE(env); return; } } while (0)
#endif
-
diff --git a/core/jni/android/graphics/LargeBitmap.cpp b/core/jni/android/graphics/LargeBitmap.cpp
deleted file mode 100644
index 4cf5dfa..0000000
--- a/core/jni/android/graphics/LargeBitmap.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-#define LOG_TAG "LargeBitmap"
-
-#include "SkBitmap.h"
-#include "SkImageEncoder.h"
-#include "SkColorPriv.h"
-#include "GraphicsJNI.h"
-#include "SkDither.h"
-#include "SkUnPreMultiply.h"
-#include "SkUtils.h"
-#include "SkTemplates.h"
-#include "SkPixelRef.h"
-#include "BitmapFactory.h"
-#include "AutoDecodeCancel.h"
-#include "SkLargeBitmap.h"
-
-#include <binder/Parcel.h>
-#include "android_util_Binder.h"
-#include "android_nio_utils.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-
-#include <jni.h>
-
-#if 0
- #define TRACE_BITMAP(code) code
-#else
- #define TRACE_BITMAP(code)
-#endif
-
-static jobject nullObjectReturn(const char msg[]) {
- if (msg) {
- SkDebugf("--- %s\n", msg);
- }
- return NULL;
-}
-
-/*
- * nine patch not supported
- *
- * purgeable not supported
- * reportSizeToVM not supported
- */
-static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkLargeBitmap *bm,
- int start_x, int start_y, int width, int height, jobject options) {
- SkImageDecoder *decoder = bm->getDecoder();
- int sampleSize = 1;
- SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
- bool doDither = true;
-
- if (NULL != options) {
- sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
- // initialize these, in case we fail later on
- env->SetIntField(options, gOptions_widthFieldID, -1);
- env->SetIntField(options, gOptions_heightFieldID, -1);
- env->SetObjectField(options, gOptions_mimeFieldID, 0);
-
- jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
- prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
- doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
- }
-
- decoder->setDitherImage(doDither);
- SkBitmap* bitmap = new SkBitmap;
- SkAutoTDelete<SkBitmap> adb(bitmap);
- AutoDecoderCancel adc(options, decoder);
-
- // To fix the race condition in case "requestCancelDecode"
- // happens earlier than AutoDecoderCancel object is added
- // to the gAutoDecoderCancelMutex linked list.
- if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
- return nullObjectReturn("gOptions_mCancelID");;
- }
-
- SkIRect region;
- region.fLeft = start_x;
- region.fTop = start_y;
- region.fRight = start_x + width;
- region.fBottom = start_y + height;
-
- if (!bm->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
- return nullObjectReturn("decoder->decodeRegion returned false");
- }
-
- // update options (if any)
- if (NULL != options) {
- env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
- env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
- // TODO: set the mimeType field with the data from the codec.
- // but how to reuse a set of strings, rather than allocating new one
- // each time?
- env->SetObjectField(options, gOptions_mimeFieldID,
- getMimeTypeString(env, decoder->getFormat()));
- }
-
- // detach bitmap from its autotdeleter, since we want to own it now
- adb.detach();
-
- SkPixelRef* pr;
- pr = bitmap->pixelRef();
- // promise we will never change our pixels (great for sharing and pictures)
- pr->setImmutable();
- // now create the java bitmap
- return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
-}
-
-static int nativeGetHeight(JNIEnv* env, jobject, SkLargeBitmap *bm) {
- return bm->getHeight();
-}
-
-static int nativeGetWidth(JNIEnv* env, jobject, SkLargeBitmap *bm) {
- return bm->getWidth();
-}
-
-static void nativeClean(JNIEnv* env, jobject, SkLargeBitmap *bm) {
- delete bm;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-#include <android_runtime/AndroidRuntime.h>
-
-static JNINativeMethod gLargeBitmapMethods[] = {
- { "nativeDecodeRegion",
- "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
- (void*)nativeDecodeRegion},
- { "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
- { "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
- { "nativeClean", "(I)V", (void*)nativeClean},
-};
-
-#define kClassPathName "android/graphics/LargeBitmap"
-
-int register_android_graphics_LargeBitmap(JNIEnv* env);
-int register_android_graphics_LargeBitmap(JNIEnv* env)
-{
- return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
- gLargeBitmapMethods, SK_ARRAY_COUNT(gLargeBitmapMethods));
-}
-
diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp
new file mode 100644
index 0000000..b6ead19
--- /dev/null
+++ b/core/jni/android/graphics/Utils.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "Utils.h"
+#include "SkUtils.h"
+
+using namespace android;
+
+bool AssetStreamAdaptor::rewind() {
+ off_t pos = fAsset->seek(0, SEEK_SET);
+ if (pos == (off_t)-1) {
+ SkDebugf("----- fAsset->seek(rewind) failed\n");
+ return false;
+ }
+ return true;
+}
+
+size_t AssetStreamAdaptor::read(void* buffer, size_t size) {
+ ssize_t amount;
+
+ if (NULL == buffer) {
+ if (0 == size) { // caller is asking us for our total length
+ return fAsset->getLength();
+ }
+ // asset->seek returns new total offset
+ // we want to return amount that was skipped
+
+ off_t oldOffset = fAsset->seek(0, SEEK_CUR);
+ if (-1 == oldOffset) {
+ SkDebugf("---- fAsset->seek(oldOffset) failed\n");
+ return 0;
+ }
+ off_t newOffset = fAsset->seek(size, SEEK_CUR);
+ if (-1 == newOffset) {
+ SkDebugf("---- fAsset->seek(%d) failed\n", size);
+ return 0;
+ }
+ amount = newOffset - oldOffset;
+ } else {
+ amount = fAsset->read(buffer, size);
+ if (amount <= 0) {
+ SkDebugf("---- fAsset->read(%d) returned %d\n", size, amount);
+ }
+ }
+
+ if (amount < 0) {
+ amount = 0;
+ }
+ return amount;
+}
+
+jobject android::nullObjectReturn(const char msg[]) {
+ if (msg) {
+ SkDebugf("--- %s\n", msg);
+ }
+ return NULL;
+}
diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h
new file mode 100644
index 0000000..2de41a1
--- /dev/null
+++ b/core/jni/android/graphics/Utils.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#ifndef UTILS_DEFINED
+#define UTILS_DEFINED
+
+#include "SkStream.h"
+
+#include "android_util_Binder.h"
+
+#include <jni.h>
+#include <utils/Asset.h>
+
+namespace android {
+
+class AssetStreamAdaptor : public SkStream {
+public:
+ AssetStreamAdaptor(Asset* a) : fAsset(a) {}
+ virtual bool rewind();
+ virtual size_t read(void* buffer, size_t size);
+
+private:
+ Asset* fAsset;
+};
+
+
+/** Restore the file descriptor's offset in our destructor
+ */
+class AutoFDSeek {
+public:
+ AutoFDSeek(int fd) : fFD(fd) {
+ fCurr = ::lseek(fd, 0, SEEK_CUR);
+ }
+ ~AutoFDSeek() {
+ if (fCurr >= 0) {
+ ::lseek(fFD, fCurr, SEEK_SET);
+ }
+ }
+private:
+ int fFD;
+ off_t fCurr;
+};
+
+jobject nullObjectReturn(const char msg[]);
+
+}; // namespace android
+
+#endif
diff --git a/core/res/res/drawable-hdpi/stat_notify_usb_debugger.png b/core/res/res/drawable-hdpi/stat_notify_usb_debugger.png
deleted file mode 100755
index fdf6c6c..0000000
--- a/core/res/res/drawable-hdpi/stat_notify_usb_debugger.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_adb.png b/core/res/res/drawable-hdpi/stat_sys_adb.png
old mode 100644
new mode 100755
index aef8650..fdf6c6c
--- a/core/res/res/drawable-hdpi/stat_sys_adb.png
+++ b/core/res/res/drawable-hdpi/stat_sys_adb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png b/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png
index bdcb378..4fbfa4f 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png
index 0876bc6..b07c7bc 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png
index fa27ee4..de2f3c3 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png
index 343e4ca..b5eab83 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 61f6ba3..4cdf4f6f 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -202,10 +202,12 @@
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.StatusBar.EventContent">
+ <item name="android:textColor">#ff6b6b6b</item>
</style>
<style name="TextAppearance.StatusBar.EventContent.Title">
<item name="android:textSize">16sp</item>
<item name="android:textStyle">bold</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
</style>
<!-- Widget Styles -->
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 6234f2c..7b49bc5 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -581,132 +581,6 @@
nativeSetDefaultConfig(config.nativeInt);
}
- /**
- * Create a LargeBitmap from the specified byte array.
- * Currently only the Jpeg format is supported.
- *
- * @param data byte array of compressed image data.
- * @param offset offset into data for where the decoder should begin
- * parsing.
- * @param length the number of bytes, beginning at offset, to parse
- * @param isShareable If this is true, then the LargeBitmap may keep a
- * shallow reference to the input. If this is false,
- * then the LargeBitmap will explicitly make a copy of the
- * input data, and keep that. Even if sharing is allowed,
- * the implementation may still decide to make a deep
- * copy of the input data. If an image is progressively encoded,
- * allowing sharing may degrade the decoding speed.
- * @return LargeBitmap, or null if the image data could not be decoded.
- * @throws IOException if the image format is not supported or can not be decoded.
- * @hide
- */
- public static LargeBitmap createLargeBitmap(byte[] data,
- int offset, int length, boolean isShareable) throws IOException {
- if ((offset | length) < 0 || data.length < offset + length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- return nativeCreateLargeBitmap(data, offset, length, isShareable);
- }
-
- /**
- * Create a LargeBitmap from the file descriptor.
- * The position within the descriptor will not be changed when
- * this returns, so the descriptor can be used again as is.
- * Currently only the Jpeg format is supported.
- *
- * @param fd The file descriptor containing the data to decode
- * @param isShareable If this is true, then the LargeBitmap may keep a
- * shallow reference to the input. If this is false,
- * then the LargeBitmap will explicitly make a copy of the
- * input data, and keep that. Even if sharing is allowed,
- * the implementation may still decide to make a deep
- * copy of the input data. If an image is progressively encoded,
- * allowing sharing may degrade the decoding speed.
- * @return LargeBitmap, or null if the image data could not be decoded.
- * @throws IOException if the image format is not supported or can not be decoded.
- * @hide
- */
- public static LargeBitmap createLargeBitmap(
- FileDescriptor fd, boolean isShareable) throws IOException {
- return nativeCreateLargeBitmap(fd, isShareable);
- }
-
- /**
- * Create a LargeBitmap from an input stream.
- * The stream's position will be where ever it was after the encoded data
- * was read.
- * Currently only the Jpeg format is supported.
- *
- * @param is The input stream that holds the raw data to be decoded into a
- * LargeBitmap.
- * @param isShareable If this is true, then the LargeBitmap may keep a
- * shallow reference to the input. If this is false,
- * then the LargeBitmap will explicitly make a copy of the
- * input data, and keep that. Even if sharing is allowed,
- * the implementation may still decide to make a deep
- * copy of the input data. If an image is progressively encoded,
- * allowing sharing may degrade the decoding speed.
- * @return LargeBitmap, or null if the image data could not be decoded.
- * @throws IOException if the image format is not supported or can not be decoded.
- * @hide
- */
- public static LargeBitmap createLargeBitmap(InputStream is,
- boolean isShareable) throws IOException {
- // we need mark/reset to work properly in JNI
-
- if (!is.markSupported()) {
- is = new BufferedInputStream(is, 16 * 1024);
- }
-
- if (is instanceof AssetManager.AssetInputStream) {
- return nativeCreateLargeBitmap(
- ((AssetManager.AssetInputStream) is).getAssetInt(),
- isShareable);
- } else {
- // pass some temp storage down to the native code. 1024 is made up,
- // but should be large enough to avoid too many small calls back
- // into is.read(...).
- byte [] tempStorage = new byte[16 * 1024];
- return nativeCreateLargeBitmap(is, tempStorage, isShareable);
- }
- }
-
- /**
- * Create a LargeBitmap from a file path.
- * Currently only the Jpeg format is supported.
- *
- * @param pathName complete path name for the file to be decoded.
- * @param isShareable If this is true, then the LargeBitmap may keep a
- * shallow reference to the input. If this is false,
- * then the LargeBitmap will explicitly make a copy of the
- * input data, and keep that. Even if sharing is allowed,
- * the implementation may still decide to make a deep
- * copy of the input data. If an image is progressively encoded,
- * allowing sharing may degrade the decoding speed.
- * @return LargeBitmap, or null if the image data could not be decoded.
- * @throws IOException if the image format is not supported or can not be decoded.
- * @hide
- */
- public static LargeBitmap createLargeBitmap(String pathName, boolean isShareable)
- throws IOException {
- LargeBitmap bm = null;
- InputStream stream = null;
-
- try {
- stream = new FileInputStream(pathName);
- bm = createLargeBitmap(stream, isShareable);
- } finally {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- // do nothing here
- }
- }
- }
- return bm;
- }
-
private static native void nativeSetDefaultConfig(int nativeConfig);
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts);
@@ -716,14 +590,4 @@
private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
int length, Options opts);
private static native byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad);
-
- private static native LargeBitmap nativeCreateLargeBitmap(
- byte[] data, int offset, int length, boolean isShareable);
- private static native LargeBitmap nativeCreateLargeBitmap(
- FileDescriptor fd, boolean isShareable);
- private static native LargeBitmap nativeCreateLargeBitmap(
- InputStream is, byte[] storage, boolean isShareable);
- private static native LargeBitmap nativeCreateLargeBitmap(
- int asset, boolean isShareable);
}
-
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
new file mode 100644
index 0000000..454eb4a
--- /dev/null
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -0,0 +1,263 @@
+/* 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.graphics;
+
+import android.content.res.AssetManager;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * BitmapRegionDecoder can be used to decode a rectangle region from an image.
+ * BitmapRegionDecoder is particularly useful when an original image is large and
+ * you only need parts of the image.
+ *
+ * <p>To create a BitmapRegionDecoder, call newInstance(...).
+ * Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly
+ * to get a decoded Bitmap of the specified region.
+ *
+ */
+public final class BitmapRegionDecoder {
+ private int mNativeBitmapRegionDecoder;
+ private boolean mRecycled;
+
+ /**
+ * Create a BitmapRegionDecoder from the specified byte array.
+ * Currently only the Jpeg format is supported.
+ *
+ * @param data byte array of compressed image data.
+ * @param offset offset into data for where the decoder should begin
+ * parsing.
+ * @param length the number of bytes, beginning at offset, to parse
+ * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+ * shallow reference to the input. If this is false,
+ * then the BitmapRegionDecoder will explicitly make a copy of the
+ * input data, and keep that. Even if sharing is allowed,
+ * the implementation may still decide to make a deep
+ * copy of the input data. If an image is progressively encoded,
+ * allowing sharing may degrade the decoding speed.
+ * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+ * @throws IOException if the image format is not supported or can not be decoded.
+ */
+ public static BitmapRegionDecoder newInstance(byte[] data,
+ int offset, int length, boolean isShareable) throws IOException {
+ if ((offset | length) < 0 || data.length < offset + length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ return nativeNewInstance(data, offset, length, isShareable);
+ }
+
+ /**
+ * Create a BitmapRegionDecoder from the file descriptor.
+ * The position within the descriptor will not be changed when
+ * this returns, so the descriptor can be used again as is.
+ * Currently only the Jpeg format is supported.
+ *
+ * @param fd The file descriptor containing the data to decode
+ * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+ * shallow reference to the input. If this is false,
+ * then the BitmapRegionDecoder will explicitly make a copy of the
+ * input data, and keep that. Even if sharing is allowed,
+ * the implementation may still decide to make a deep
+ * copy of the input data. If an image is progressively encoded,
+ * allowing sharing may degrade the decoding speed.
+ * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+ * @throws IOException if the image format is not supported or can not be decoded.
+ */
+ public static BitmapRegionDecoder newInstance(
+ FileDescriptor fd, boolean isShareable) throws IOException {
+ return nativeNewInstance(fd, isShareable);
+ }
+
+ /**
+ * Create a BitmapRegionDecoder from an input stream.
+ * The stream's position will be where ever it was after the encoded data
+ * was read.
+ * Currently only the Jpeg format is supported.
+ *
+ * @param is The input stream that holds the raw data to be decoded into a
+ * BitmapRegionDecoder.
+ * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+ * shallow reference to the input. If this is false,
+ * then the BitmapRegionDecoder will explicitly make a copy of the
+ * input data, and keep that. Even if sharing is allowed,
+ * the implementation may still decide to make a deep
+ * copy of the input data. If an image is progressively encoded,
+ * allowing sharing may degrade the decoding speed.
+ * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+ * @throws IOException if the image format is not supported or can not be decoded.
+ */
+ public static BitmapRegionDecoder newInstance(InputStream is,
+ boolean isShareable) throws IOException {
+ // we need mark/reset to work properly in JNI
+
+ if (!is.markSupported()) {
+ is = new BufferedInputStream(is, 16 * 1024);
+ }
+
+ if (is instanceof AssetManager.AssetInputStream) {
+ return nativeNewInstance(
+ ((AssetManager.AssetInputStream) is).getAssetInt(),
+ isShareable);
+ } else {
+ // pass some temp storage down to the native code. 1024 is made up,
+ // but should be large enough to avoid too many small calls back
+ // into is.read(...).
+ byte [] tempStorage = new byte[16 * 1024];
+ return nativeNewInstance(is, tempStorage, isShareable);
+ }
+ }
+
+ /**
+ * Create a BitmapRegionDecoder from a file path.
+ * Currently only the Jpeg format is supported.
+ *
+ * @param pathName complete path name for the file to be decoded.
+ * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+ * shallow reference to the input. If this is false,
+ * then the BitmapRegionDecoder will explicitly make a copy of the
+ * input data, and keep that. Even if sharing is allowed,
+ * the implementation may still decide to make a deep
+ * copy of the input data. If an image is progressively encoded,
+ * allowing sharing may degrade the decoding speed.
+ * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+ * @throws IOException if the image format is not supported or can not be decoded.
+ */
+ public static BitmapRegionDecoder newInstance(String pathName,
+ boolean isShareable) throws IOException {
+ BitmapRegionDecoder decoder = null;
+ InputStream stream = null;
+
+ try {
+ stream = new FileInputStream(pathName);
+ decoder = newInstance(stream, isShareable);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // do nothing here
+ }
+ }
+ }
+ return decoder;
+ }
+
+ /* Private constructor that must receive an already allocated native
+ region decoder int (pointer).
+
+ This can be called from JNI code.
+ */
+ private BitmapRegionDecoder(int decoder) {
+ mNativeBitmapRegionDecoder = decoder;
+ mRecycled = false;
+ }
+
+ /**
+ * Decodes a rectangle region in the image specified by rect.
+ *
+ * @param rect The rectangle that specified the region to be decode.
+ * @param options null-ok; Options that control downsampling.
+ * inPurgeable is not supported.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded.
+ */
+ public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
+ checkRecycled("decodeRegion called on recycled region decoder");
+ if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
+ || rect.bottom > getHeight())
+ throw new IllegalArgumentException("rectangle is not inside the image");
+ return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
+ rect.right - rect.left, rect.bottom - rect.top, options);
+ }
+
+ /** Returns the original image's width */
+ public int getWidth() {
+ checkRecycled("getWidth called on recycled region decoder");
+ return nativeGetWidth(mNativeBitmapRegionDecoder);
+ }
+
+ /** Returns the original image's height */
+ public int getHeight() {
+ checkRecycled("getHeight called on recycled region decoder");
+ return nativeGetHeight(mNativeBitmapRegionDecoder);
+ }
+
+ /**
+ * Frees up the memory associated with this region decoder, and mark the
+ * region decoder as "dead", meaning it will throw an exception if decodeRegion(),
+ * getWidth() or getHeight() is called.
+ *
+ * <p>This operation cannot be reversed, so it should only be called if you are
+ * sure there are no further uses for the region decoder. This is an advanced call,
+ * and normally need not be called, since the normal GC process will free up this
+ * memory when there are no more references to this region decoder.
+ */
+ public void recycle() {
+ if (!mRecycled) {
+ nativeClean(mNativeBitmapRegionDecoder);
+ mRecycled = true;
+ }
+ }
+
+ /**
+ * Returns true if this region decoder has been recycled.
+ * If so, then it is an error to try use its method.
+ *
+ * @return true if the region decoder has been recycled
+ */
+ public final boolean isRecycled() {
+ return mRecycled;
+ }
+
+ /**
+ * Called by methods that want to throw an exception if the region decoder
+ * has already been recycled.
+ */
+ private void checkRecycled(String errorMessage) {
+ if (mRecycled) {
+ throw new IllegalStateException(errorMessage);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ recycle();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static native Bitmap nativeDecodeRegion(int lbm,
+ int start_x, int start_y, int width, int height,
+ BitmapFactory.Options options);
+ private static native int nativeGetWidth(int lbm);
+ private static native int nativeGetHeight(int lbm);
+ private static native void nativeClean(int lbm);
+
+ private static native BitmapRegionDecoder nativeNewInstance(
+ byte[] data, int offset, int length, boolean isShareable);
+ private static native BitmapRegionDecoder nativeNewInstance(
+ FileDescriptor fd, boolean isShareable);
+ private static native BitmapRegionDecoder nativeNewInstance(
+ InputStream is, byte[] storage, boolean isShareable);
+ private static native BitmapRegionDecoder nativeNewInstance(
+ int asset, boolean isShareable);
+}
diff --git a/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal.9.png b/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal.9.png
old mode 100644
new mode 100755
index baafed6..6d3ea9a
--- a/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable.9.png b/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable.9.png
old mode 100644
new mode 100755
index 175197b..2646ba0
--- a/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png b/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png
old mode 100644
new mode 100755
index ec1feff..013210c
--- a/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/btn_default_small_pressed.9.png b/packages/SystemUI/res/drawable-hdpi/btn_default_small_pressed.9.png
old mode 100644
new mode 100755
index c1f9a0f..24cefd4
--- a/packages/SystemUI/res/drawable-hdpi/btn_default_small_pressed.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/btn_default_small_pressed.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/btn_default_small_selected.9.png b/packages/SystemUI/res/drawable-hdpi/btn_default_small_selected.9.png
old mode 100644
new mode 100755
index 0ea3f40..bedbceb
--- a/packages/SystemUI/res/drawable-hdpi/btn_default_small_selected.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/btn_default_small_selected.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/statusbar_background.9.png b/packages/SystemUI/res/drawable-hdpi/statusbar_background.9.png
index 67c8b7f..98e5487 100644
--- a/packages/SystemUI/res/drawable-hdpi/statusbar_background.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/statusbar_background.9.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 423cf5a..a5d6885 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -21,7 +21,7 @@
<!-- android:background="@drawable/status_bar_closed_default_background" -->
<com.android.systemui.statusbar.StatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:background="@drawable/status_bar_background"
+ android:background="@drawable/statusbar_background"
android:orientation="vertical"
android:focusable="true"
android:descendantFocusability="afterDescendants"
@@ -108,6 +108,6 @@
android:gravity="center_vertical|left"
android:paddingLeft="6px"
android:paddingRight="6px"
- android:background="@drawable/status_bar_background"
+ android:background="@drawable/statusbar_background"
/>
</com.android.systemui.statusbar.StatusBarView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index b5b1b50..a58e311 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,7 +31,7 @@
android:paddingTop="3dp"
android:paddingBottom="5dp"
android:paddingRight="3dp"
- android:background="@drawable/shade_header_background"
+ android:background="@drawable/title_bar_portrait"
>
<com.android.systemui.statusbar.CarrierLabel
android:layout_width="0dp"
@@ -43,7 +43,7 @@
android:paddingBottom="1dp"
android:paddingLeft="4dp"
android:textAppearance="?android:attr/textAppearanceLarge"
- android:textColor="?android:attr/textColorSecondary"
+ android:textColor="#ffdfdfdf"
/>
<TextView android:id="@+id/clear_all_button"
android:layout_width="wrap_content"
@@ -82,7 +82,7 @@
<TextView android:id="@+id/noNotificationsTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/title_bar_portrait"
+ android:background="@drawable/shade_bgcolor"
android:paddingLeft="5dp"
android:textAppearance="@style/TextAppearance.StatusBar.Title"
android:text="@string/status_bar_no_notifications_title"
@@ -91,7 +91,7 @@
<TextView android:id="@+id/ongoingTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/title_bar_portrait"
+ android:background="@drawable/shade_bgcolor"
android:paddingLeft="5dp"
android:textAppearance="@style/TextAppearance.StatusBar.Title"
android:text="@string/status_bar_ongoing_events_title"
@@ -105,7 +105,7 @@
<TextView android:id="@+id/latestTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/title_bar_portrait"
+ android:background="@drawable/shade_bgcolor"
android:paddingLeft="5dp"
android:textAppearance="@style/TextAppearance.StatusBar.Title"
android:text="@string/status_bar_latest_events_title"
diff --git a/packages/SystemUI/res/layout/status_bar_tracking.xml b/packages/SystemUI/res/layout/status_bar_tracking.xml
index a2b40e6..3b9b775 100644
--- a/packages/SystemUI/res/layout/status_bar_tracking.xml
+++ b/packages/SystemUI/res/layout/status_bar_tracking.xml
@@ -26,11 +26,12 @@
android:paddingRight="0px"
>
- <com.android.systemui.statusbar.TrackingPatternView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- />
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="#ff212121"
+ />
<com.android.systemui.statusbar.CloseDragHandle android:id="@+id/close"
android:layout_width="match_parent"
@@ -42,7 +43,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:scaleType="fitXY"
- android:src="@drawable/shade_handlebar"
+ android:src="@drawable/status_bar_close_on"
/>
</com.android.systemui.statusbar.CloseDragHandle>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
new file mode 100644
index 0000000..4b1fcf0f
--- /dev/null
+++ b/packages/SystemUI/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 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.
+ */
+-->
+<resources>
+ <drawable name="shade_bgcolor">#ff282828</drawable>
+ <drawable name="notification_header_text_color">#ff969696</drawable>
+</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 816f34a4..86bcf3a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -19,7 +19,7 @@
<style name="TextAppearance.StatusBar.Title" parent="@android:style/TextAppearance.StatusBar">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:textStyle">bold</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">@drawable/notification_header_text_color</item>
</style>
<style name="TextAppearance.StatusBar.IntruderAlert"
diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
index 14b7d3e..9829f9a 100644
--- a/services/java/com/android/server/DropBoxManagerService.java
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -36,6 +36,7 @@
import com.android.internal.os.IDropBoxManagerService;
+import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -179,7 +180,10 @@
// the data in uncompressed form.
temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
- output = new FileOutputStream(temp);
+ int bufferSize = mBlockSize;
+ if (bufferSize > 4096) bufferSize = 4096;
+ if (bufferSize < 512) bufferSize = 512;
+ output = new BufferedOutputStream(new FileOutputStream(temp), bufferSize);
if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {
output = new GZIPOutputStream(output);
flags = flags | DropBoxManager.IS_GZIPPED;
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index f3cb9b7..7b68d68 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -180,7 +180,8 @@
}
}
- private void sendCommand(String command) {
+ private void sendCommand(String command)
+ throws NativeDaemonConnectorException {
sendCommand(command, null);
}
@@ -190,11 +191,13 @@
* @param command The command to send to the daemon
* @param argument The argument to send with the command (or null)
*/
- private void sendCommand(String command, String argument) {
+ private void sendCommand(String command, String argument)
+ throws NativeDaemonConnectorException {
synchronized (this) {
if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
if (mOutputStream == null) {
Slog.e(TAG, "No connection to daemon", new IllegalStateException());
+ throw new NativeDaemonConnectorException("No output stream!");
} else {
StringBuilder builder = new StringBuilder(command);
if (argument != null) {
@@ -224,6 +227,7 @@
while (!complete) {
try {
+ // TODO - this should not block forever
String line = mResponseQueue.take();
if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
String[] tokens = line.split(" ");
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 4a69f20..33b19d6 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -47,6 +47,7 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.concurrent.CountDownLatch;
/**
* @hide
@@ -54,7 +55,7 @@
class NetworkManagementService extends INetworkManagementService.Stub {
private static final String TAG = "NetworkManagmentService";
-
+ private static final boolean DBG = true;
private static final String NETD_TAG = "NetdConnector";
class NetdResponseCode {
@@ -86,6 +87,9 @@
*/
private NativeDaemonConnector mConnector;
+ private Thread mThread;
+ private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
+
private ArrayList<INetworkManagementEventObserver> mObservers;
/**
@@ -93,9 +97,8 @@
*
* @param context Binder context for this service
*/
- public NetworkManagementService(Context context) {
+ private NetworkManagementService(Context context) {
mContext = context;
-
mObservers = new ArrayList<INetworkManagementEventObserver>();
if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
@@ -104,8 +107,17 @@
mConnector = new NativeDaemonConnector(
new NetdCallbackReceiver(), "netd", 10, NETD_TAG);
- Thread thread = new Thread(mConnector, NETD_TAG);
- thread.start();
+ mThread = new Thread(mConnector, NETD_TAG);
+ }
+
+ public static NetworkManagementService create(Context context) throws InterruptedException {
+ NetworkManagementService service = new NetworkManagementService(context);
+ if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
+ service.mThread.start();
+ if (DBG) Slog.d(TAG, "Awaiting socket connection");
+ service.mConnectedSignal.await();
+ if (DBG) Slog.d(TAG, "Connected");
+ return service;
}
public void registerObserver(INetworkManagementEventObserver obs) {
@@ -157,6 +169,14 @@
}
}
+ /**
+ * Let us know the daemon is connected
+ */
+ protected void onConnected() {
+ if (DBG) Slog.d(TAG, "onConnected");
+ mConnectedSignal.countDown();
+ }
+
//
// Netd Callback handling
@@ -164,6 +184,7 @@
class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
public void onDaemonConnected() {
+ NetworkManagementService.this.onConnected();
new Thread() {
public void run() {
}
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 8ab1bb8..5d32b74 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -25,6 +25,7 @@
import android.content.BroadcastReceiver;
import android.content.ContentQueryMap;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -36,6 +37,7 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Environment;
@@ -109,6 +111,9 @@
// Cached secure settings; see updateSettingsValues()
private int mShortKeylightDelay = SHORT_KEYLIGHT_DELAY_DEFAULT;
+ // Default timeout for screen off, if not found in settings database = 15 seconds.
+ private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15000;
+
// flags for setPowerState
private static final int SCREEN_ON_BIT = 0x00000001;
private static final int SCREEN_BRIGHT_BIT = 0x00000002;
@@ -411,24 +416,28 @@
}
private class SettingsObserver implements Observer {
- private int getInt(String name) {
- return mSettings.getValues(name).getAsInteger(Settings.System.VALUE);
+ private int getInt(String name, int defValue) {
+ ContentValues values = mSettings.getValues(name);
+ Integer iVal = values != null ? values.getAsInteger(Settings.System.VALUE) : null;
+ return iVal != null ? iVal : defValue;
}
public void update(Observable o, Object arg) {
synchronized (mLocks) {
- // STAY_ON_WHILE_PLUGGED_IN
- mStayOnConditions = getInt(STAY_ON_WHILE_PLUGGED_IN);
+ // STAY_ON_WHILE_PLUGGED_IN, default to when plugged into AC
+ mStayOnConditions = getInt(STAY_ON_WHILE_PLUGGED_IN,
+ BatteryManager.BATTERY_PLUGGED_AC);
updateWakeLockLocked();
- // SCREEN_OFF_TIMEOUT
- mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT);
+ // SCREEN_OFF_TIMEOUT, default to 15 seconds
+ mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT);
// DIM_SCREEN
//mDimScreen = getInt(DIM_SCREEN) != 0;
- // SCREEN_BRIGHTNESS_MODE
- setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE));
+ // SCREEN_BRIGHTNESS_MODE, default to manual
+ setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL));
// recalculate everything
setScreenOffTimeoutsLocked();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 859de46..9475005 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -254,7 +254,8 @@
try {
Slog.i(TAG, "NetworkManagement Service");
ServiceManager.addService(
- Context.NETWORKMANAGEMENT_SERVICE, new NetworkManagementService(context));
+ Context.NETWORKMANAGEMENT_SERVICE,
+ NetworkManagementService.create(context));
} catch (Throwable e) {
Slog.e(TAG, "Failure starting NetworkManagement Service", e);
}
diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java
index 89e6aa9..4851a46 100644
--- a/voip/java/android/net/rtp/AudioCodec.java
+++ b/voip/java/android/net/rtp/AudioCodec.java
@@ -16,41 +16,133 @@
package android.net.rtp;
-/** @hide */
+import java.util.Arrays;
+
+/**
+ * This class defines a collection of audio codecs to be used with
+ * {@link AudioStream}s. Their parameters are designed to be exchanged using
+ * Session Description Protocol (SDP). Most of the values listed here can be
+ * found in RFC 3551, while others are described in separated standards.
+ *
+ * <p>Few simple configurations are defined as public static instances for the
+ * convenience of direct uses. More complicated ones could be obtained using
+ * {@link #getCodec(int, String, String)}. For example, one can use the
+ * following snippet to create a mode-1-only AMR codec.</p>
+ * <pre>
+ * AudioCodec codec = AudioCodec.getCodec(100, "AMR/8000", "mode-set=1");
+ * </pre>
+ *
+ * @see AudioStream
+ * @hide
+ */
public class AudioCodec {
- public static final AudioCodec ULAW = new AudioCodec("PCMU", 8000, 160, 0);
- public static final AudioCodec ALAW = new AudioCodec("PCMA", 8000, 160, 8);
+ /**
+ * The RTP payload type of the encoding.
+ */
+ public final int type;
/**
- * Returns system supported codecs.
+ * The encoding parameters to be used in the corresponding SDP attribute.
*/
- public static AudioCodec[] getSystemSupportedCodecs() {
- return new AudioCodec[] {AudioCodec.ULAW, AudioCodec.ALAW};
+ public final String rtpmap;
+
+ /**
+ * The format parameters to be used in the corresponding SDP attribute.
+ */
+ public final String fmtp;
+
+ /**
+ * G.711 u-law audio codec.
+ */
+ public static final AudioCodec PCMU = new AudioCodec(0, "PCMU/8000", null);
+
+ /**
+ * G.711 a-law audio codec.
+ */
+ public static final AudioCodec PCMA = new AudioCodec(8, "PCMA/8000", null);
+
+ /**
+ * GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or
+ * simply FR.
+ */
+ public static final AudioCodec GSM = new AudioCodec(3, "GSM/8000", null);
+
+ /**
+ * GSM Enhanced Full-Rate audio codec, also known as GSM-EFR, GSM 06.60, or
+ * simply EFR.
+ */
+ public static final AudioCodec GSM_EFR = new AudioCodec(96, "GSM-EFR/8000", null);
+
+ /**
+ * Adaptive Multi-Rate narrowband audio codec, also known as AMR or AMR-NB.
+ * Currently CRC, robust sorting, and interleaving are not supported. See
+ * more details about these features in RFC 4867.
+ */
+ public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
+
+ // TODO: add rest of the codecs when the native part is done.
+ private static final AudioCodec[] sCodecs = {PCMU, PCMA};
+
+ private AudioCodec(int type, String rtpmap, String fmtp) {
+ this.type = type;
+ this.rtpmap = rtpmap;
+ this.fmtp = fmtp;
}
/**
- * Returns the codec instance if it is supported by the system.
+ * Returns system supported audio codecs.
+ */
+ public static AudioCodec[] getCodecs() {
+ return Arrays.copyOf(sCodecs, sCodecs.length);
+ }
+
+ /**
+ * Creates an AudioCodec according to the given configuration.
*
- * @param name name of the codec
- * @return the matched codec or null if the codec name is not supported by
- * the system
+ * @param type The payload type of the encoding defined in RTP/AVP.
+ * @param rtpmap The encoding parameters specified in the corresponding SDP
+ * attribute, or null if it is not available.
+ * @param fmtp The format parameters specified in the corresponding SDP
+ * attribute, or null if it is not available.
+ * @return The configured AudioCodec or {@code null} if it is not supported.
*/
- public static AudioCodec getSystemSupportedCodec(String name) {
- for (AudioCodec codec : getSystemSupportedCodecs()) {
- if (codec.name.equals(name)) return codec;
+ public static AudioCodec getCodec(int type, String rtpmap, String fmtp) {
+ if (type < 0 || type > 127) {
+ return null;
}
- return null;
- }
- public final String name;
- public final int sampleRate;
- public final int sampleCount;
- public final int defaultType;
+ AudioCodec hint = null;
+ if (rtpmap != null) {
+ String clue = rtpmap.trim().toUpperCase();
+ for (AudioCodec codec : sCodecs) {
+ if (clue.startsWith(codec.rtpmap)) {
+ String channels = clue.substring(codec.rtpmap.length());
+ if (channels.length() == 0 || channels.equals("/1")) {
+ hint = codec;
+ }
+ break;
+ }
+ }
+ } else if (type < 96) {
+ for (AudioCodec codec : sCodecs) {
+ if (type == codec.type) {
+ hint = codec;
+ rtpmap = codec.rtpmap;
+ break;
+ }
+ }
+ }
- private AudioCodec(String name, int sampleRate, int sampleCount, int defaultType) {
- this.name = name;
- this.sampleRate = sampleRate;
- this.sampleCount = sampleCount;
- this.defaultType = defaultType;
+ if (hint == null) {
+ return null;
+ }
+ if (hint == AMR && fmtp != null) {
+ String clue = fmtp.toLowerCase();
+ if (clue.contains("crc=1") || clue.contains("robust-sorting=1") ||
+ clue.contains("interleaving=")) {
+ return null;
+ }
+ }
+ return new AudioCodec(type, rtpmap, fmtp);
}
}
diff --git a/voip/java/android/net/rtp/AudioGroup.java b/voip/java/android/net/rtp/AudioGroup.java
index 37cc121..43a3827 100644
--- a/voip/java/android/net/rtp/AudioGroup.java
+++ b/voip/java/android/net/rtp/AudioGroup.java
@@ -20,13 +20,63 @@
import java.util.Map;
/**
+ * An AudioGroup acts as a router connected to the speaker, the microphone, and
+ * {@link AudioStream}s. Its pipeline has four steps. First, for each
+ * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
+ * packets and stores in its buffer. Then, if the microphone is enabled,
+ * processes the recorded audio and stores in its buffer. Third, if the speaker
+ * is enabled, mixes and playbacks buffers of all AudioStreams. Finally, for
+ * each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
+ * buffers and sends back the encoded packets. An AudioGroup does nothing if
+ * there is no AudioStream in it.
+ *
+ * <p>Few things must be noticed before using these classes. The performance is
+ * highly related to the system load and the network bandwidth. Usually a
+ * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network
+ * bandwidth, and vise versa. Using two AudioStreams at the same time not only
+ * doubles the load but also the bandwidth. The condition varies from one device
+ * to another, and developers must choose the right combination in order to get
+ * the best result.
+ *
+ * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For
+ * example, a Voice over IP (VoIP) application might want to put a conference
+ * call on hold in order to make a new call but still allow people in the
+ * previous call to talk to each other. This can be done easily using two
+ * AudioGroups, but there are some limitations. Since the speaker and the
+ * microphone are shared globally, only one AudioGroup is allowed to run in
+ * modes other than {@link #MODE_ON_HOLD}. In addition, before adding an
+ * AudioStream into an AudioGroup, one should always put all other AudioGroups
+ * into {@link #MODE_ON_HOLD}. That will make sure the audio driver correctly
+ * initialized.
+ * @hide
*/
-/** @hide */
public class AudioGroup {
+ /**
+ * This mode is similar to {@link #MODE_NORMAL} except the speaker and
+ * the microphone are disabled.
+ */
public static final int MODE_ON_HOLD = 0;
+
+ /**
+ * This mode is similar to {@link #MODE_NORMAL} except the microphone is
+ * muted.
+ */
public static final int MODE_MUTED = 1;
+
+ /**
+ * This mode indicates that the speaker, the microphone, and all
+ * {@link AudioStream}s in the group are enabled. First, the packets
+ * received from the streams are decoded and mixed with the audio recorded
+ * from the microphone. Then, the results are played back to the speaker,
+ * encoded and sent back to each stream.
+ */
public static final int MODE_NORMAL = 2;
- public static final int MODE_EC_ENABLED = 3;
+
+ /**
+ * This mode is similar to {@link #MODE_NORMAL} except the echo suppression
+ * is enabled. It should be only used when the speaker phone is on.
+ */
+ public static final int MODE_ECHO_SUPPRESSION = 3;
private final Map<AudioStream, Integer> mStreams;
private int mMode = MODE_ON_HOLD;
@@ -36,23 +86,42 @@
System.loadLibrary("rtp_jni");
}
+ /**
+ * Creates an empty AudioGroup.
+ */
public AudioGroup() {
mStreams = new HashMap<AudioStream, Integer>();
}
+ /**
+ * Returns the current mode.
+ */
public int getMode() {
return mMode;
}
+ /**
+ * Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
+ * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
+ * {@link #MODE_ECHO_SUPPRESSION}.
+ *
+ * @param mode The mode to change to.
+ * @throws IllegalArgumentException if the mode is invalid.
+ */
public synchronized native void setMode(int mode);
- synchronized void add(AudioStream stream, AudioCodec codec, int codecType, int dtmfType) {
+ private native void add(int mode, int socket, String remoteAddress,
+ int remotePort, String codecSpec, int dtmfType);
+
+ synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) {
if (!mStreams.containsKey(stream)) {
try {
int socket = stream.dup();
+ String codecSpec = String.format("%d %s %s", codec.type,
+ codec.rtpmap, codec.fmtp);
add(stream.getMode(), socket,
- stream.getRemoteAddress().getHostAddress(), stream.getRemotePort(),
- codec.name, codec.sampleRate, codec.sampleCount, codecType, dtmfType);
+ stream.getRemoteAddress().getHostAddress(),
+ stream.getRemotePort(), codecSpec, dtmfType);
mStreams.put(stream, socket);
} catch (NullPointerException e) {
throw new IllegalStateException(e);
@@ -60,8 +129,7 @@
}
}
- private native void add(int mode, int socket, String remoteAddress, int remotePort,
- String codecName, int sampleRate, int sampleCount, int codecType, int dtmfType);
+ private native void remove(int socket);
synchronized void remove(AudioStream stream) {
Integer socket = mStreams.remove(stream);
@@ -70,8 +138,6 @@
}
}
- private native void remove(int socket);
-
/**
* Sends a DTMF digit to every {@link AudioStream} in this group. Currently
* only event {@code 0} to {@code 15} are supported.
@@ -80,13 +146,16 @@
*/
public native synchronized void sendDtmf(int event);
- public synchronized void reset() {
+ /**
+ * Removes every {@link AudioStream} in this group.
+ */
+ public synchronized void clear() {
remove(-1);
}
@Override
protected void finalize() throws Throwable {
- reset();
+ clear();
super.finalize();
}
}
diff --git a/voip/java/android/net/rtp/AudioStream.java b/voip/java/android/net/rtp/AudioStream.java
index a955fd2..e5197ce 100644
--- a/voip/java/android/net/rtp/AudioStream.java
+++ b/voip/java/android/net/rtp/AudioStream.java
@@ -20,12 +20,27 @@
import java.net.SocketException;
/**
- * AudioStream represents a RTP stream carrying audio payloads.
+ * An AudioStream is a {@link RtpStream} which carrys audio payloads over
+ * Real-time Transport Protocol (RTP). Two different classes are developed in
+ * order to support various usages such as audio conferencing. An AudioStream
+ * represents a remote endpoint which consists of a network mapping and a
+ * configured {@link AudioCodec}. On the other side, An {@link AudioGroup}
+ * represents a local endpoint which mixes all the AudioStreams and optionally
+ * interacts with the speaker and the microphone at the same time. The simplest
+ * usage includes one for each endpoints. For other combinations, users should
+ * be aware of the limitations described in {@link AudioGroup}.
+ *
+ * <p>An AudioStream becomes busy when it joins an AudioGroup. In this case most
+ * of the setter methods are disabled. This is designed to ease the task of
+ * managing native resources. One can always make an AudioStream leave its
+ * AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it
+ * back after the modification is done.
+ *
+ * @see AudioGroup
+ * @hide
*/
-/** @hide */
public class AudioStream extends RtpStream {
private AudioCodec mCodec;
- private int mCodecType = -1;
private int mDtmfType = -1;
private AudioGroup mGroup;
@@ -42,7 +57,8 @@
}
/**
- * Returns {@code true} if the stream already joined an {@link AudioGroup}.
+ * Returns {@code true} if the stream has already joined an
+ * {@link AudioGroup}.
*/
@Override
public final boolean isBusy() {
@@ -52,7 +68,7 @@
/**
* Returns the joined {@link AudioGroup}.
*/
- public AudioGroup getAudioGroup() {
+ public AudioGroup getGroup() {
return mGroup;
}
@@ -74,35 +90,45 @@
mGroup = null;
}
if (group != null) {
- group.add(this, mCodec, mCodecType, mDtmfType);
+ group.add(this, mCodec, mDtmfType);
mGroup = group;
}
}
/**
- * Sets the {@link AudioCodec} and its RTP payload type. According to RFC
- * 3551, the type must be in the range of 0 and 127, where 96 and above are
- * dynamic types. For codecs with static mappings (non-negative
- * {@link AudioCodec#defaultType}), assigning a different non-dynamic type
- * is disallowed.
+ * Returns the {@link AudioCodec}, or {@code null} if it is not set.
+ *
+ * @see #setCodec(AudioCodec)
+ */
+ public AudioCodec getCodec() {
+ return mCodec;
+ }
+
+ /**
+ * Sets the {@link AudioCodec}.
*
* @param codec The AudioCodec to be used.
- * @param type The RTP payload type.
- * @throws IllegalArgumentException if the type is invalid or used by DTMF.
+ * @throws IllegalArgumentException if its type is used by DTMF.
* @throws IllegalStateException if the stream is busy.
*/
- public void setCodec(AudioCodec codec, int type) {
+ public void setCodec(AudioCodec codec) {
if (isBusy()) {
throw new IllegalStateException("Busy");
}
- if (type < 0 || type > 127 || (type != codec.defaultType && type < 96)) {
- throw new IllegalArgumentException("Invalid type");
- }
- if (type == mDtmfType) {
+ if (codec.type == mDtmfType) {
throw new IllegalArgumentException("The type is used by DTMF");
}
mCodec = codec;
- mCodecType = type;
+ }
+
+ /**
+ * Returns the RTP payload type for dual-tone multi-frequency (DTMF) digits,
+ * or {@code -1} if it is not enabled.
+ *
+ * @see #setDtmfType(int)
+ */
+ public int getDtmfType() {
+ return mDtmfType;
}
/**
@@ -111,7 +137,7 @@
* certain tasks, such as second-stage dialing. According to RFC 2833, the
* RTP payload type for DTMF is assigned dynamically, so it must be in the
* range of 96 and 127. One can use {@code -1} to disable DTMF and free up
- * the previous assigned value. This method cannot be called when the stream
+ * the previous assigned type. This method cannot be called when the stream
* already joined an {@link AudioGroup}.
*
* @param type The RTP payload type to be used or {@code -1} to disable it.
@@ -127,7 +153,7 @@
if (type < 96 || type > 127) {
throw new IllegalArgumentException("Invalid type");
}
- if (type == mCodecType) {
+ if (type == mCodec.type) {
throw new IllegalArgumentException("The type is used by codec");
}
}
diff --git a/voip/java/android/net/rtp/RtpStream.java b/voip/java/android/net/rtp/RtpStream.java
index ef5ca17..23fb258 100644
--- a/voip/java/android/net/rtp/RtpStream.java
+++ b/voip/java/android/net/rtp/RtpStream.java
@@ -22,13 +22,25 @@
import java.net.SocketException;
/**
- * RtpStream represents a base class of media streams running over
- * Real-time Transport Protocol (RTP).
+ * RtpStream represents the base class of streams which send and receive network
+ * packets with media payloads over Real-time Transport Protocol (RTP).
+ * @hide
*/
-/** @hide */
public class RtpStream {
+ /**
+ * This mode indicates that the stream sends and receives packets at the
+ * same time. This is the initial mode for new streams.
+ */
public static final int MODE_NORMAL = 0;
+
+ /**
+ * This mode indicates that the stream only sends packets.
+ */
public static final int MODE_SEND_ONLY = 1;
+
+ /**
+ * This mode indicates that the stream only receives packets.
+ */
public static final int MODE_RECEIVE_ONLY = 2;
private final InetAddress mLocalAddress;
@@ -89,15 +101,16 @@
}
/**
- * Returns {@code true} if the stream is busy. This method is intended to be
- * overridden by subclasses.
+ * Returns {@code true} if the stream is busy. In this case most of the
+ * setter methods are disabled. This method is intended to be overridden
+ * by subclasses.
*/
public boolean isBusy() {
return false;
}
/**
- * Returns the current mode. The initial mode is {@link #MODE_NORMAL}.
+ * Returns the current mode.
*/
public int getMode() {
return mMode;
@@ -123,7 +136,8 @@
}
/**
- * Associates with a remote host.
+ * Associates with a remote host. This defines the destination of the
+ * outgoing packets.
*
* @param address The network address of the remote host.
* @param port The network port of the remote host.
diff --git a/voip/java/android/net/sip/SimpleSessionDescription.java b/voip/java/android/net/sip/SimpleSessionDescription.java
new file mode 100644
index 0000000..29166dc
--- /dev/null
+++ b/voip/java/android/net/sip/SimpleSessionDescription.java
@@ -0,0 +1,612 @@
+/*
+ * 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.net.sip;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * An object used to manipulate messages of Session Description Protocol (SDP).
+ * It is mainly designed for the uses of Session Initiation Protocol (SIP).
+ * Therefore, it only handles connection addresses ("c="), bandwidth limits,
+ * ("b="), encryption keys ("k="), and attribute fields ("a="). Currently this
+ * implementation does not support multicast sessions.
+ *
+ * <p>Here is an example code to create a session description.</p>
+ * <pre>
+ * SimpleSessionDescription description = new SimpleSessionDescription(
+ * System.currentTimeMillis(), "1.2.3.4");
+ * Media media = description.newMedia("audio", 56789, 1, "RTP/AVP");
+ * media.setRtpPayload(0, "PCMU/8000", null);
+ * media.setRtpPayload(8, "PCMA/8000", null);
+ * media.setRtpPayload(127, "telephone-event/8000", "0-15");
+ * media.setAttribute("sendrecv", "");
+ * </pre>
+ * <p>Invoking <code>description.encode()</code> will produce a result like the
+ * one below.</p>
+ * <pre>
+ * v=0
+ * o=- 1284970442706 1284970442709 IN IP4 1.2.3.4
+ * s=-
+ * c=IN IP4 1.2.3.4
+ * t=0 0
+ * m=audio 56789 RTP/AVP 0 8 127
+ * a=rtpmap:0 PCMU/8000
+ * a=rtpmap:8 PCMA/8000
+ * a=rtpmap:127 telephone-event/8000
+ * a=fmtp:127 0-15
+ * a=sendrecv
+ * </pre>
+ * @hide
+ */
+public class SimpleSessionDescription {
+ private final Fields mFields = new Fields("voscbtka");
+ private final ArrayList<Media> mMedia = new ArrayList<Media>();
+
+ /**
+ * Creates a minimal session description from the given session ID and
+ * unicast address. The address is used in the origin field ("o=") and the
+ * connection field ("c="). See {@link SimpleSessionDescription} for an
+ * example of its usage.
+ */
+ public SimpleSessionDescription(long sessionId, String address) {
+ address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + address;
+ mFields.parse("v=0");
+ mFields.parse(String.format("o=- %d %d %s", sessionId,
+ System.currentTimeMillis(), address));
+ mFields.parse("s=-");
+ mFields.parse("t=0 0");
+ mFields.parse("c=" + address);
+ }
+
+ /**
+ * Creates a session description from the given message.
+ *
+ * @throws IllegalArgumentException if message is invalid.
+ */
+ public SimpleSessionDescription(String message) {
+ String[] lines = message.trim().replaceAll(" +", " ").split("[\r\n]+");
+ Fields fields = mFields;
+
+ for (String line : lines) {
+ try {
+ if (line.charAt(1) != '=') {
+ throw new IllegalArgumentException();
+ }
+ if (line.charAt(0) == 'm') {
+ String[] parts = line.substring(2).split(" ", 4);
+ String[] ports = parts[1].split("/", 2);
+ Media media = newMedia(parts[0], Integer.parseInt(ports[0]),
+ (ports.length < 2) ? 1 : Integer.parseInt(ports[1]),
+ parts[2]);
+ for (String format : parts[3].split(" ")) {
+ media.setFormat(format, null);
+ }
+ fields = media;
+ } else {
+ fields.parse(line);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid SDP: " + line);
+ }
+ }
+ }
+
+ /**
+ * Creates a new media description in this session description.
+ *
+ * @param type The media type, e.g. {@code "audio"}.
+ * @param port The first transport port used by this media.
+ * @param portCount The number of contiguous ports used by this media.
+ * @param protocol The transport protocol, e.g. {@code "RTP/AVP"}.
+ */
+ public Media newMedia(String type, int port, int portCount,
+ String protocol) {
+ Media media = new Media(type, port, portCount, protocol);
+ mMedia.add(media);
+ return media;
+ }
+
+ /**
+ * Returns all the media descriptions in this session description.
+ */
+ public Media[] getMedia() {
+ return mMedia.toArray(new Media[mMedia.size()]);
+ }
+
+ /**
+ * Encodes the session description and all its media descriptions in a
+ * string. Note that the result might be incomplete if a required field
+ * has never been added before.
+ */
+ public String encode() {
+ StringBuilder buffer = new StringBuilder();
+ mFields.write(buffer);
+ for (Media media : mMedia) {
+ media.write(buffer);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the connection address or {@code null} if it is not present.
+ */
+ public String getAddress() {
+ return mFields.getAddress();
+ }
+
+ /**
+ * Sets the connection address. The field will be removed if the address
+ * is {@code null}.
+ */
+ public void setAddress(String address) {
+ mFields.setAddress(address);
+ }
+
+ /**
+ * Returns the encryption method or {@code null} if it is not present.
+ */
+ public String getEncryptionMethod() {
+ return mFields.getEncryptionMethod();
+ }
+
+ /**
+ * Returns the encryption key or {@code null} if it is not present.
+ */
+ public String getEncryptionKey() {
+ return mFields.getEncryptionKey();
+ }
+
+ /**
+ * Sets the encryption method and the encryption key. The field will be
+ * removed if the method is {@code null}.
+ */
+ public void setEncryption(String method, String key) {
+ mFields.setEncryption(method, key);
+ }
+
+ /**
+ * Returns the types of the bandwidth limits.
+ */
+ public String[] getBandwidthTypes() {
+ return mFields.getBandwidthTypes();
+ }
+
+ /**
+ * Returns the bandwidth limit of the given type or {@code -1} if it is not
+ * present.
+ */
+ public int getBandwidth(String type) {
+ return mFields.getBandwidth(type);
+ }
+
+ /**
+ * Sets the bandwith limit for the given type. The field will be removed if
+ * the value is negative.
+ */
+ public void setBandwidth(String type, int value) {
+ mFields.setBandwidth(type, value);
+ }
+
+ /**
+ * Returns the names of all the attributes.
+ */
+ public String[] getAttributeNames() {
+ return mFields.getAttributeNames();
+ }
+
+ /**
+ * Returns the attribute of the given name or {@code null} if it is not
+ * present.
+ */
+ public String getAttribute(String name) {
+ return mFields.getAttribute(name);
+ }
+
+ /**
+ * Sets the attribute for the given name. The field will be removed if
+ * the value is {@code null}. To set a binary attribute, use an empty
+ * string as the value.
+ */
+ public void setAttribute(String name, String value) {
+ mFields.setAttribute(name, value);
+ }
+
+ /**
+ * This class represents a media description of a session description. It
+ * can only be created by {@link SimpleSessionDescription#newMedia}. Since
+ * the syntax is more restricted for RTP based protocols, two sets of access
+ * methods are implemented. See {@link SimpleSessionDescription} for an
+ * example of its usage.
+ */
+ public static class Media extends Fields {
+ private final String mType;
+ private final int mPort;
+ private final int mPortCount;
+ private final String mProtocol;
+ private ArrayList<String> mFormats = new ArrayList<String>();
+
+ private Media(String type, int port, int portCount, String protocol) {
+ super("icbka");
+ mType = type;
+ mPort = port;
+ mPortCount = portCount;
+ mProtocol = protocol;
+ }
+
+ /**
+ * Returns the media type.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the first transport port used by this media.
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Returns the number of contiguous ports used by this media.
+ */
+ public int getPortCount() {
+ return mPortCount;
+ }
+
+ /**
+ * Returns the transport protocol.
+ */
+ public String getProtocol() {
+ return mProtocol;
+ }
+
+ /**
+ * Returns the media formats.
+ */
+ public String[] getFormats() {
+ return mFormats.toArray(new String[mFormats.size()]);
+ }
+
+ /**
+ * Returns the {@code fmtp} attribute of the given format or
+ * {@code null} if it is not present.
+ */
+ public String getFmtp(String format) {
+ return super.get("a=fmtp:" + format, ' ');
+ }
+
+ /**
+ * Sets a format and its {@code fmtp} attribute. If the attribute is
+ * {@code null}, the corresponding field will be removed.
+ */
+ public void setFormat(String format, String fmtp) {
+ mFormats.remove(format);
+ mFormats.add(format);
+ super.set("a=rtpmap:" + format, ' ', null);
+ super.set("a=fmtp:" + format, ' ', fmtp);
+ }
+
+ /**
+ * Removes a format and its {@code fmtp} attribute.
+ */
+ public void removeFormat(String format) {
+ mFormats.remove(format);
+ super.set("a=rtpmap:" + format, ' ', null);
+ super.set("a=fmtp:" + format, ' ', null);
+ }
+
+ /**
+ * Returns the RTP payload types.
+ */
+ public int[] getRtpPayloadTypes() {
+ int[] types = new int[mFormats.size()];
+ int length = 0;
+ for (String format : mFormats) {
+ try {
+ types[length] = Integer.parseInt(format);
+ ++length;
+ } catch (NumberFormatException e) { }
+ }
+ return Arrays.copyOf(types, length);
+ }
+
+ /**
+ * Returns the {@code rtpmap} attribute of the given RTP payload type
+ * or {@code null} if it is not present.
+ */
+ public String getRtpmap(int type) {
+ return super.get("a=rtpmap:" + type, ' ');
+ }
+
+ /**
+ * Returns the {@code fmtp} attribute of the given RTP payload type or
+ * {@code null} if it is not present.
+ */
+ public String getFmtp(int type) {
+ return super.get("a=fmtp:" + type, ' ');
+ }
+
+ /**
+ * Sets a RTP payload type and its {@code rtpmap} and {@code fmtp}
+ * attributes. If any of the attributes is {@code null}, the
+ * corresponding field will be removed. See
+ * {@link SimpleSessionDescription} for an example of its usage.
+ */
+ public void setRtpPayload(int type, String rtpmap, String fmtp) {
+ String format = String.valueOf(type);
+ mFormats.remove(format);
+ mFormats.add(format);
+ super.set("a=rtpmap:" + format, ' ', rtpmap);
+ super.set("a=fmtp:" + format, ' ', fmtp);
+ }
+
+ /**
+ * Removes a RTP payload and its {@code rtpmap} and {@code fmtp}
+ * attributes.
+ */
+ public void removeRtpPayload(int type) {
+ removeFormat(String.valueOf(type));
+ }
+
+ private void write(StringBuilder buffer) {
+ buffer.append("m=").append(mType).append(' ').append(mPort);
+ if (mPortCount != 1) {
+ buffer.append('/').append(mPortCount);
+ }
+ buffer.append(' ').append(mProtocol);
+ for (String format : mFormats) {
+ buffer.append(' ').append(format);
+ }
+ buffer.append("\r\n");
+ super.write(buffer);
+ }
+ }
+
+ /**
+ * This class acts as a set of fields, and the size of the set is expected
+ * to be small. Therefore, it uses a simple list instead of maps. Each field
+ * has three parts: a key, a delimiter, and a value. Delimiters are special
+ * because they are not included in binary attributes. As a result, the
+ * private methods, which are the building blocks of this class, all take
+ * the delimiter as an argument.
+ */
+ private static class Fields {
+ private final String mOrder;
+ private final ArrayList<String> mLines = new ArrayList<String>();
+
+ Fields(String order) {
+ mOrder = order;
+ }
+
+ /**
+ * Returns the connection address or {@code null} if it is not present.
+ */
+ public String getAddress() {
+ String address = get("c", '=');
+ if (address == null) {
+ return null;
+ }
+ String[] parts = address.split(" ");
+ if (parts.length != 3) {
+ return null;
+ }
+ int slash = parts[2].indexOf('/');
+ return (slash < 0) ? parts[2] : parts[2].substring(0, slash);
+ }
+
+ /**
+ * Sets the connection address. The field will be removed if the address
+ * is {@code null}.
+ */
+ public void setAddress(String address) {
+ if (address != null) {
+ address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") +
+ address;
+ }
+ set("c", '=', address);
+ }
+
+ /**
+ * Returns the encryption method or {@code null} if it is not present.
+ */
+ public String getEncryptionMethod() {
+ String encryption = get("k", '=');
+ if (encryption == null) {
+ return null;
+ }
+ int colon = encryption.indexOf(':');
+ return (colon == -1) ? encryption : encryption.substring(0, colon);
+ }
+
+ /**
+ * Returns the encryption key or {@code null} if it is not present.
+ */
+ public String getEncryptionKey() {
+ String encryption = get("k", '=');
+ if (encryption == null) {
+ return null;
+ }
+ int colon = encryption.indexOf(':');
+ return (colon == -1) ? null : encryption.substring(0, colon + 1);
+ }
+
+ /**
+ * Sets the encryption method and the encryption key. The field will be
+ * removed if the method is {@code null}.
+ */
+ public void setEncryption(String method, String key) {
+ set("k", '=', (method == null || key == null) ?
+ method : method + ':' + key);
+ }
+
+ /**
+ * Returns the types of the bandwidth limits.
+ */
+ public String[] getBandwidthTypes() {
+ return cut("b=", ':');
+ }
+
+ /**
+ * Returns the bandwidth limit of the given type or {@code -1} if it is
+ * not present.
+ */
+ public int getBandwidth(String type) {
+ String value = get("b=" + type, ':');
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) { }
+ setBandwidth(type, -1);
+ }
+ return -1;
+ }
+
+ /**
+ * Sets the bandwith limit for the given type. The field will be removed
+ * if the value is negative.
+ */
+ public void setBandwidth(String type, int value) {
+ set("b=" + type, ':', (value < 0) ? null : String.valueOf(value));
+ }
+
+ /**
+ * Returns the names of all the attributes.
+ */
+ public String[] getAttributeNames() {
+ return cut("a=", ':');
+ }
+
+ /**
+ * Returns the attribute of the given name or {@code null} if it is not
+ * present.
+ */
+ public String getAttribute(String name) {
+ return get("a=" + name, ':');
+ }
+
+ /**
+ * Sets the attribute for the given name. The field will be removed if
+ * the value is {@code null}. To set a binary attribute, use an empty
+ * string as the value.
+ */
+ public void setAttribute(String name, String value) {
+ set("a=" + name, ':', value);
+ }
+
+ private void write(StringBuilder buffer) {
+ for (int i = 0; i < mOrder.length(); ++i) {
+ char type = mOrder.charAt(i);
+ for (String line : mLines) {
+ if (line.charAt(0) == type) {
+ buffer.append(line).append("\r\n");
+ }
+ }
+ }
+ }
+
+ /**
+ * Invokes {@link #set} after splitting the line into three parts.
+ */
+ private void parse(String line) {
+ char type = line.charAt(0);
+ if (mOrder.indexOf(type) == -1) {
+ return;
+ }
+ char delimiter = '=';
+ if (line.startsWith("a=rtpmap:") || line.startsWith("a=fmtp:")) {
+ delimiter = ' ';
+ } else if (type == 'b' || type == 'a') {
+ delimiter = ':';
+ }
+ int i = line.indexOf(delimiter);
+ if (i == -1) {
+ set(line, delimiter, "");
+ } else {
+ set(line.substring(0, i), delimiter, line.substring(i + 1));
+ }
+ }
+
+ /**
+ * Finds the key with the given prefix and returns its suffix.
+ */
+ private String[] cut(String prefix, char delimiter) {
+ String[] names = new String[mLines.size()];
+ int length = 0;
+ for (String line : mLines) {
+ if (line.startsWith(prefix)) {
+ int i = line.indexOf(delimiter);
+ if (i == -1) {
+ i = line.length();
+ }
+ names[length] = line.substring(prefix.length(), i);
+ ++length;
+ }
+ }
+ return Arrays.copyOf(names, length);
+ }
+
+ /**
+ * Returns the index of the key.
+ */
+ private int find(String key, char delimiter) {
+ int length = key.length();
+ for (int i = mLines.size() - 1; i >= 0; --i) {
+ String line = mLines.get(i);
+ if (line.startsWith(key) && (line.length() == length ||
+ line.charAt(length) == delimiter)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Sets the key with the value or removes the key if the value is
+ * {@code null}.
+ */
+ private void set(String key, char delimiter, String value) {
+ int index = find(key, delimiter);
+ if (value != null) {
+ if (value.length() != 0) {
+ key = key + delimiter + value;
+ }
+ if (index == -1) {
+ mLines.add(key);
+ } else {
+ mLines.set(index, key);
+ }
+ } else if (index != -1) {
+ mLines.remove(index);
+ }
+ }
+
+ /**
+ * Returns the value of the key.
+ */
+ private String get(String key, char delimiter) {
+ int index = find(key, delimiter);
+ if (index == -1) {
+ return null;
+ }
+ String line = mLines.get(index);
+ int length = key.length();
+ return (line.length() == length) ? "" : line.substring(length + 1);
+ }
+ }
+}
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index ccf4d15..2f8d175 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -16,8 +16,6 @@
package android.net.sip;
-import gov.nist.javax.sdp.fields.SDPKeywords;
-
import android.content.Context;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -28,6 +26,7 @@
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
import android.net.rtp.RtpStream;
+import android.net.sip.SimpleSessionDescription.Media;
import android.net.wifi.WifiManager;
import android.os.Message;
import android.os.RemoteException;
@@ -38,15 +37,13 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import javax.sdp.SdpException;
/**
- * Class that handles an audio call over SIP.
+ * Class that handles an audio call over SIP.
*/
/** @hide */
public class SipAudioCallImpl extends SipSessionAdapter
@@ -54,20 +51,19 @@
private static final String TAG = SipAudioCallImpl.class.getSimpleName();
private static final boolean RELEASE_SOCKET = true;
private static final boolean DONT_RELEASE_SOCKET = false;
- private static final String AUDIO = "audio";
- private static final int DTMF = 101;
private static final int SESSION_TIMEOUT = 5; // in seconds
private Context mContext;
private SipProfile mLocalProfile;
private SipAudioCall.Listener mListener;
private ISipSession mSipSession;
- private SdpSessionDescription mPeerSd;
+
+ private long mSessionId = System.currentTimeMillis();
+ private String mPeerSd;
private AudioStream mAudioStream;
private AudioGroup mAudioGroup;
- private SdpSessionDescription.AudioCodec mCodec;
- private long mSessionId = -1L; // SDP session ID
+
private boolean mInCall = false;
private boolean mMuted = false;
private boolean mHold = false;
@@ -149,7 +145,7 @@
mInCall = false;
mHold = false;
- mSessionId = -1L;
+ mSessionId = System.currentTimeMillis();
mErrorCode = SipErrorCode.NO_ERROR;
mErrorMessage = null;
@@ -229,8 +225,8 @@
// session changing request
try {
- mPeerSd = new SdpSessionDescription(sessionDescription);
- answerCall(SESSION_TIMEOUT);
+ String answer = createAnswer(sessionDescription).encode();
+ mSipSession.answerCall(answer, SESSION_TIMEOUT);
} catch (Throwable e) {
Log.e(TAG, "onRinging()", e);
session.endCall();
@@ -245,12 +241,8 @@
String sessionDescription) {
stopRingbackTone();
stopRinging();
- try {
- mPeerSd = new SdpSessionDescription(sessionDescription);
- Log.d(TAG, "sip call established: " + mPeerSd);
- } catch (SdpException e) {
- Log.e(TAG, "createSessionDescription()", e);
- }
+ mPeerSd = sessionDescription;
+ Log.v(TAG, "onCallEstablished()" + mPeerSd);
Listener listener = mListener;
if (listener != null) {
@@ -335,10 +327,10 @@
public synchronized void attachCall(ISipSession session,
String sessionDescription) throws SipException {
mSipSession = session;
+ mPeerSd = sessionDescription;
+ Log.v(TAG, "attachCall()" + mPeerSd);
try {
- mPeerSd = new SdpSessionDescription(sessionDescription);
session.setListener(this);
-
if (getState() == SipSessionState.INCOMING_CALL) startRinging();
} catch (Throwable e) {
Log.e(TAG, "attachCall()", e);
@@ -354,8 +346,8 @@
throw new SipException(
"Failed to create SipSession; network available?");
}
- mSipSession.makeCall(peerProfile, createOfferSessionDescription(),
- timeout);
+ mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+ mSipSession.makeCall(peerProfile, createOffer().encode(), timeout);
} catch (Throwable e) {
if (e instanceof SipException) {
throw (SipException) e;
@@ -368,7 +360,7 @@
public synchronized void endCall() throws SipException {
try {
stopRinging();
- stopCall(true);
+ stopCall(RELEASE_SOCKET);
mInCall = false;
// perform the above local ops first and then network op
@@ -378,72 +370,131 @@
}
}
- public synchronized void holdCall(int timeout) throws SipException {
- if (mHold) return;
- try {
- mSipSession.changeCall(createHoldSessionDescription(), timeout);
- mHold = true;
- } catch (Throwable e) {
- throwSipException(e);
- }
-
- AudioGroup audioGroup = getAudioGroup();
- if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
- }
-
public synchronized void answerCall(int timeout) throws SipException {
try {
stopRinging();
- mSipSession.answerCall(createAnswerSessionDescription(), timeout);
+ mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+ mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
} catch (Throwable e) {
Log.e(TAG, "answerCall()", e);
throwSipException(e);
}
}
- public synchronized void continueCall(int timeout) throws SipException {
- if (!mHold) return;
+ public synchronized void holdCall(int timeout) throws SipException {
+ if (mHold) return;
try {
- mHold = false;
- mSipSession.changeCall(createContinueSessionDescription(), timeout);
+ mSipSession.changeCall(createHoldOffer().encode(), timeout);
} catch (Throwable e) {
throwSipException(e);
}
+ mHold = true;
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+ }
+ public synchronized void continueCall(int timeout) throws SipException {
+ if (!mHold) return;
+ try {
+ mSipSession.changeCall(createContinueOffer().encode(), timeout);
+ } catch (Throwable e) {
+ throwSipException(e);
+ }
+ mHold = false;
AudioGroup audioGroup = getAudioGroup();
if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
}
- private String createOfferSessionDescription() {
- AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs();
- return createSdpBuilder(true, convert(codecs)).build();
+ private SimpleSessionDescription createOffer() {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ AudioCodec[] codecs = AudioCodec.getCodecs();
+ Media media = offer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ for (AudioCodec codec : AudioCodec.getCodecs()) {
+ media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+ }
+ media.setRtpPayload(127, "telephone-event/8000", "0-15");
+ return offer;
}
- private String createAnswerSessionDescription() {
- try {
- // choose an acceptable media from mPeerSd to answer
- SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd);
- SdpSessionDescription.Builder sdpBuilder =
- createSdpBuilder(false, codec);
- if (mPeerSd.isSendOnly(AUDIO)) {
- sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null);
- } else if (mPeerSd.isReceiveOnly(AUDIO)) {
- sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null);
+ private SimpleSessionDescription createAnswer(String offerSd) {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(offerSd);
+ SimpleSessionDescription answer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ AudioCodec codec = null;
+ for (Media media : offer.getMedia()) {
+ if ((codec == null) && (media.getPort() > 0)
+ && "audio".equals(media.getType())
+ && "RTP/AVP".equals(media.getProtocol())) {
+ // Find the first audio codec we supported.
+ for (int type : media.getRtpPayloadTypes()) {
+ codec = AudioCodec.getCodec(type, media.getRtpmap(type),
+ media.getFmtp(type));
+ if (codec != null) {
+ break;
+ }
+ }
+ if (codec != null) {
+ Media reply = answer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+
+ // Check if DTMF is supported in the same media.
+ for (int type : media.getRtpPayloadTypes()) {
+ String rtpmap = media.getRtpmap(type);
+ if ((type != codec.type) && (rtpmap != null)
+ && rtpmap.startsWith("telephone-event")) {
+ reply.setRtpPayload(
+ type, rtpmap, media.getFmtp(type));
+ }
+ }
+
+ // Handle recvonly and sendonly.
+ if (media.getAttribute("recvonly") != null) {
+ answer.setAttribute("sendonly", "");
+ } else if(media.getAttribute("sendonly") != null) {
+ answer.setAttribute("recvonly", "");
+ } else if(offer.getAttribute("recvonly") != null) {
+ answer.setAttribute("sendonly", "");
+ } else if(offer.getAttribute("sendonly") != null) {
+ answer.setAttribute("recvonly", "");
+ }
+ continue;
+ }
}
- return sdpBuilder.build();
- } catch (SdpException e) {
- throw new RuntimeException(e);
+ // Reject the media.
+ Media reply = answer.newMedia(
+ media.getType(), 0, 1, media.getProtocol());
+ for (String format : media.getFormats()) {
+ reply.setFormat(format, null);
+ }
}
+ if (codec == null) {
+ throw new IllegalStateException("Reject SDP: no suitable codecs");
+ }
+ return answer;
}
- private String createHoldSessionDescription() {
- try {
- return createSdpBuilder(false, mCodec)
- .addMediaAttribute(AUDIO, "sendonly", (String) null)
- .build();
- } catch (SdpException e) {
- throw new RuntimeException(e);
+ private SimpleSessionDescription createHoldOffer() {
+ SimpleSessionDescription offer = createContinueOffer();
+ offer.setAttribute("sendonly", "");
+ return offer;
+ }
+
+ private SimpleSessionDescription createContinueOffer() {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ Media media = offer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ AudioCodec codec = mAudioStream.getCodec();
+ media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+ int dtmfType = mAudioStream.getDtmfType();
+ if (dtmfType != -1) {
+ media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
}
+ return offer;
}
private void grabWifiHighPerfLock() {
@@ -468,57 +519,6 @@
return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
}
- private String createContinueSessionDescription() {
- return createSdpBuilder(true, mCodec).build();
- }
-
- private String getMediaDescription(SdpSessionDescription.AudioCodec codec) {
- return String.format("%d %s/%d", codec.payloadType, codec.name,
- codec.sampleRate);
- }
-
- private long getSessionId() {
- if (mSessionId < 0) {
- mSessionId = System.currentTimeMillis();
- }
- return mSessionId;
- }
-
- private SdpSessionDescription.Builder createSdpBuilder(
- boolean addTelephoneEvent,
- SdpSessionDescription.AudioCodec... codecs) {
- String localIp = getLocalIp();
- SdpSessionDescription.Builder sdpBuilder;
- try {
- long sessionVersion = System.currentTimeMillis();
- sdpBuilder = new SdpSessionDescription.Builder("SIP Call")
- .setOrigin(mLocalProfile, getSessionId(), sessionVersion,
- SDPKeywords.IN, SDPKeywords.IPV4, localIp)
- .setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4,
- localIp);
- List<Integer> codecIds = new ArrayList<Integer>();
- for (SdpSessionDescription.AudioCodec codec : codecs) {
- codecIds.add(codec.payloadType);
- }
- if (addTelephoneEvent) codecIds.add(DTMF);
- sdpBuilder.addMedia(AUDIO, getLocalMediaPort(), 1, "RTP/AVP",
- codecIds.toArray(new Integer[codecIds.size()]));
- for (SdpSessionDescription.AudioCodec codec : codecs) {
- sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
- getMediaDescription(codec));
- }
- if (addTelephoneEvent) {
- sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
- DTMF + " telephone-event/8000");
- }
- // FIXME: deal with vbr codec
- sdpBuilder.addMediaAttribute(AUDIO, "ptime", "20");
- } catch (SdpException e) {
- throw new RuntimeException(e);
- }
- return sdpBuilder;
- }
-
public synchronized void toggleMute() {
AudioGroup audioGroup = getAudioGroup();
if (audioGroup != null) {
@@ -557,49 +557,16 @@
public synchronized AudioGroup getAudioGroup() {
if (mAudioGroup != null) return mAudioGroup;
- return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup());
+ return ((mAudioStream == null) ? null : mAudioStream.getGroup());
}
public synchronized void setAudioGroup(AudioGroup group) {
- if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) {
+ if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
mAudioStream.join(group);
}
mAudioGroup = group;
}
- private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) {
- HashMap<String, AudioCodec> acceptableCodecs =
- new HashMap<String, AudioCodec>();
- for (AudioCodec codec : AudioCodec.getSystemSupportedCodecs()) {
- acceptableCodecs.put(codec.name, codec);
- }
- for (SdpSessionDescription.AudioCodec codec : sd.getAudioCodecs()) {
- AudioCodec matchedCodec = acceptableCodecs.get(codec.name);
- if (matchedCodec != null) return codec;
- }
- Log.w(TAG, "no common codec is found, use PCM/0");
- return convert(AudioCodec.ULAW);
- }
-
- private AudioCodec convert(SdpSessionDescription.AudioCodec codec) {
- AudioCodec c = AudioCodec.getSystemSupportedCodec(codec.name);
- return ((c == null) ? AudioCodec.ULAW : c);
- }
-
- private SdpSessionDescription.AudioCodec convert(AudioCodec codec) {
- return new SdpSessionDescription.AudioCodec(codec.defaultType,
- codec.name, codec.sampleRate, codec.sampleCount);
- }
-
- private SdpSessionDescription.AudioCodec[] convert(AudioCodec[] codecs) {
- SdpSessionDescription.AudioCodec[] copies =
- new SdpSessionDescription.AudioCodec[codecs.length];
- for (int i = 0, len = codecs.length; i < len; i++) {
- copies[i] = convert(codecs[i]);
- }
- return copies;
- }
-
public void startAudio() {
try {
startAudioInternal();
@@ -613,42 +580,77 @@
}
private synchronized void startAudioInternal() throws UnknownHostException {
+ if (mPeerSd == null) {
+ Log.v(TAG, "startAudioInternal() mPeerSd = null");
+ throw new IllegalStateException("mPeerSd = null");
+ }
+
stopCall(DONT_RELEASE_SOCKET);
mInCall = true;
- SdpSessionDescription peerSd = mPeerSd;
+
+ // Run exact the same logic in createAnswer() to setup mAudioStream.
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mPeerSd);
+ AudioStream stream = mAudioStream;
+ AudioCodec codec = null;
+ for (Media media : offer.getMedia()) {
+ if ((codec == null) && (media.getPort() > 0)
+ && "audio".equals(media.getType())
+ && "RTP/AVP".equals(media.getProtocol())) {
+ // Find the first audio codec we supported.
+ for (int type : media.getRtpPayloadTypes()) {
+ codec = AudioCodec.getCodec(
+ type, media.getRtpmap(type), media.getFmtp(type));
+ if (codec != null) {
+ break;
+ }
+ }
+
+ if (codec != null) {
+ // Associate with the remote host.
+ String address = media.getAddress();
+ if (address == null) {
+ address = offer.getAddress();
+ }
+ stream.associate(InetAddress.getByName(address),
+ media.getPort());
+
+ stream.setDtmfType(-1);
+ stream.setCodec(codec);
+ // Check if DTMF is supported in the same media.
+ for (int type : media.getRtpPayloadTypes()) {
+ String rtpmap = media.getRtpmap(type);
+ if ((type != codec.type) && (rtpmap != null)
+ && rtpmap.startsWith("telephone-event")) {
+ stream.setDtmfType(type);
+ }
+ }
+
+ // Handle recvonly and sendonly.
+ if (mHold) {
+ stream.setMode(RtpStream.MODE_NORMAL);
+ } else if (media.getAttribute("recvonly") != null) {
+ stream.setMode(RtpStream.MODE_SEND_ONLY);
+ } else if(media.getAttribute("sendonly") != null) {
+ stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+ } else if(offer.getAttribute("recvonly") != null) {
+ stream.setMode(RtpStream.MODE_SEND_ONLY);
+ } else if(offer.getAttribute("sendonly") != null) {
+ stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+ } else {
+ stream.setMode(RtpStream.MODE_NORMAL);
+ }
+ break;
+ }
+ }
+ }
+ if (codec == null) {
+ throw new IllegalStateException("Reject SDP: no suitable codecs");
+ }
+
if (isWifiOn()) grabWifiHighPerfLock();
- String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO);
- // TODO: handle multiple media fields
- int peerMediaPort = peerSd.getPeerMediaPort(AUDIO);
- Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort);
- int localPort = getLocalMediaPort();
- int sampleRate = 8000;
- int frameSize = sampleRate / 50; // 160
-
- // TODO: get sample rate from sdp
- mCodec = getCodec(peerSd);
-
- AudioStream audioStream = mAudioStream;
- audioStream.associate(InetAddress.getByName(peerMediaAddress),
- peerMediaPort);
- audioStream.setCodec(convert(mCodec), mCodec.payloadType);
- audioStream.setDtmfType(DTMF);
- Log.d(TAG, "start media: localPort=" + localPort + ", peer="
- + peerMediaAddress + ":" + peerMediaPort);
-
- audioStream.setMode(RtpStream.MODE_NORMAL);
if (!mHold) {
- // FIXME: won't work if peer is not sending nor receiving
- if (!peerSd.isSending(AUDIO)) {
- Log.d(TAG, " not receiving");
- audioStream.setMode(RtpStream.MODE_SEND_ONLY);
- }
- if (!peerSd.isReceiving(AUDIO)) {
- Log.d(TAG, " not sending");
- audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
- }
-
/* The recorder volume will be very low if the device is in
* IN_CALL mode. Therefore, we have to set the mode to NORMAL
* in order to have the normal microphone level.
@@ -668,7 +670,7 @@
// there's another AudioGroup out there that's active
} else {
if (audioGroup == null) audioGroup = new AudioGroup();
- audioStream.join(audioGroup);
+ mAudioStream.join(audioGroup);
if (mMuted) {
audioGroup.setMode(AudioGroup.MODE_MUTED);
} else {
@@ -690,24 +692,11 @@
}
}
- private int getLocalMediaPort() {
- if (mAudioStream != null) return mAudioStream.getLocalPort();
- try {
- AudioStream s = mAudioStream =
- new AudioStream(InetAddress.getByName(getLocalIp()));
- return s.getLocalPort();
- } catch (IOException e) {
- Log.w(TAG, "getLocalMediaPort(): " + e);
- throw new RuntimeException(e);
- }
- }
-
private String getLocalIp() {
try {
return mSipSession.getLocalIp();
} catch (RemoteException e) {
- // FIXME
- return "127.0.0.1";
+ throw new IllegalStateException(e);
}
}
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
index ddd07fc..4d8d36c 100644
--- a/voip/jni/rtp/AudioCodec.cpp
+++ b/voip/jni/rtp/AudioCodec.cpp
@@ -36,9 +36,9 @@
class UlawCodec : public AudioCodec
{
public:
- bool set(int sampleRate, int sampleCount) {
- mSampleCount = sampleCount;
- return sampleCount > 0;
+ int set(int sampleRate, const char *fmtp) {
+ mSampleCount = sampleRate / 50;
+ return mSampleCount;
}
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, void *payload, int length);
@@ -89,9 +89,9 @@
class AlawCodec : public AudioCodec
{
public:
- bool set(int sampleRate, int sampleCount) {
- mSampleCount = sampleCount;
- return sampleCount > 0;
+ int set(int sampleRate, const char *fmtp) {
+ mSampleCount = sampleRate / 50;
+ return mSampleCount;
}
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, void *payload, int length);
@@ -152,8 +152,10 @@
{
AudioCodecType *type = gAudioCodecTypes;
while (type->name != NULL) {
- if (strcmp(codecName, type->name) == 0) {
- return type->create();
+ if (strcasecmp(codecName, type->name) == 0) {
+ AudioCodec *codec = type->create();
+ codec->name = type->name;
+ return codec;
}
++type;
}
diff --git a/voip/jni/rtp/AudioCodec.h b/voip/jni/rtp/AudioCodec.h
index 797494c..e389255 100644
--- a/voip/jni/rtp/AudioCodec.h
+++ b/voip/jni/rtp/AudioCodec.h
@@ -22,9 +22,11 @@
class AudioCodec
{
public:
+ const char *name;
+ // Needed by destruction through base class pointers.
virtual ~AudioCodec() {}
- // Returns true if initialization succeeds.
- virtual bool set(int sampleRate, int sampleCount) = 0;
+ // Returns sampleCount or non-positive value if unsupported.
+ virtual int set(int sampleRate, const char *fmtp) = 0;
// Returns the length of payload in bytes.
virtual int encode(void *payload, int16_t *samples) = 0;
// Returns the number of decoded samples.
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 3433dcf..7cf06137 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -77,7 +77,7 @@
AudioStream();
~AudioStream();
bool set(int mode, int socket, sockaddr_storage *remote,
- const char *codecName, int sampleRate, int sampleCount,
+ AudioCodec *codec, int sampleRate, int sampleCount,
int codecType, int dtmfType);
void sendDtmf(int event);
@@ -104,6 +104,7 @@
int mSampleRate;
int mSampleCount;
int mInterval;
+ int mLogThrottle;
int16_t *mBuffer;
int mBufferMask;
@@ -140,7 +141,7 @@
}
bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
- const char *codecName, int sampleRate, int sampleCount,
+ AudioCodec *codec, int sampleRate, int sampleCount,
int codecType, int dtmfType)
{
if (mode < 0 || mode > LAST_MODE) {
@@ -148,14 +149,6 @@
}
mMode = mode;
- if (codecName) {
- mRemote = *remote;
- mCodec = newAudioCodec(codecName);
- if (!mCodec || !mCodec->set(sampleRate, sampleCount)) {
- return false;
- }
- }
-
mCodecMagic = (0x8000 | codecType) << 16;
mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16;
@@ -181,11 +174,15 @@
mDtmfEvent = -1;
mDtmfStart = 0;
- // Only take over the socket when succeeded.
+ // Only take over these things when succeeded.
mSocket = socket;
+ if (codec) {
+ mRemote = *remote;
+ mCodec = codec;
+ }
LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket,
- (codecName ? codecName : "RAW"), mSampleRate, mInterval);
+ (codec ? codec->name : "RAW"), mSampleRate, mInterval);
return true;
}
@@ -282,7 +279,10 @@
chain = chain->mNext;
}
if (!mixed) {
- LOGD("stream[%d] no data", mSocket);
+ if ((mTick ^ mLogThrottle) >> 10) {
+ mLogThrottle = mTick;
+ LOGD("stream[%d] no data", mSocket);
+ }
return;
}
@@ -831,10 +831,9 @@
void add(JNIEnv *env, jobject thiz, jint mode,
jint socket, jstring jRemoteAddress, jint remotePort,
- jstring jCodecName, jint sampleRate, jint sampleCount,
- jint codecType, jint dtmfType)
+ jstring jCodecSpec, jint dtmfType)
{
- const char *codecName = NULL;
+ AudioCodec *codec = NULL;
AudioStream *stream = NULL;
AudioGroup *group = NULL;
@@ -842,33 +841,42 @@
sockaddr_storage remote;
if (parse(env, jRemoteAddress, remotePort, &remote) < 0) {
// Exception already thrown.
- goto error;
+ return;
}
- if (sampleRate < 0 || sampleCount < 0 || codecType < 0 || codecType > 127) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- goto error;
+ if (!jCodecSpec) {
+ jniThrowNullPointerException(env, "codecSpec");
+ return;
}
- if (!jCodecName) {
- jniThrowNullPointerException(env, "codecName");
- goto error;
- }
- codecName = env->GetStringUTFChars(jCodecName, NULL);
- if (!codecName) {
+ const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL);
+ if (!codecSpec) {
// Exception already thrown.
+ return;
+ }
+
+ // Create audio codec.
+ int codecType = -1;
+ char codecName[16];
+ int sampleRate = -1;
+ sscanf(codecSpec, "%d %[^/]%*c%d", &codecType, codecName, &sampleRate);
+ codec = newAudioCodec(codecName);
+ int sampleCount = (codec ? codec->set(sampleRate, codecSpec) : -1);
+ env->ReleaseStringUTFChars(jCodecSpec, codecSpec);
+ if (sampleCount <= 0) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "cannot initialize audio codec");
goto error;
}
// Create audio stream.
stream = new AudioStream;
- if (!stream->set(mode, socket, &remote, codecName, sampleRate, sampleCount,
+ if (!stream->set(mode, socket, &remote, codec, sampleRate, sampleCount,
codecType, dtmfType)) {
jniThrowException(env, "java/lang/IllegalStateException",
"cannot initialize audio stream");
- env->ReleaseStringUTFChars(jCodecName, codecName);
goto error;
}
- env->ReleaseStringUTFChars(jCodecName, codecName);
socket = -1;
+ codec = NULL;
// Create audio group.
group = (AudioGroup *)env->GetIntField(thiz, gNative);
@@ -896,6 +904,7 @@
error:
delete group;
delete stream;
+ delete codec;
close(socket);
env->SetIntField(thiz, gNative, NULL);
}
@@ -930,7 +939,7 @@
}
JNINativeMethod gMethods[] = {
- {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)V", (void *)add},
+ {"add", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add},
{"remove", "(I)V", (void *)remove},
{"setMode", "(I)V", (void *)setMode},
{"sendDtmf", "(I)V", (void *)sendDtmf},