Merge change 2411 into donut
* changes:
Manual merge from cupcake_dcm. Need to be reviewed by enf.
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index 4e6859c..a3ab641 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -245,7 +245,7 @@
sp<ICameraClient> oldClient;
{
Mutex::Autolock _l(mLock);
- if (mClientPid != 0) {
+ if (mClientPid != 0 && checkPid() != NO_ERROR) {
LOGW("Tried to connect to locked camera");
return -EBUSY;
}
diff --git a/cmds/keystore/Android.mk b/cmds/keystore/Android.mk
new file mode 100644
index 0000000..20f4adf
--- /dev/null
+++ b/cmds/keystore/Android.mk
@@ -0,0 +1,21 @@
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ keystore.c commands.c
+
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, system-core)/cutils
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils
+
+LOCAL_STATIC_LIBRARIES :=
+
+LOCAL_MODULE:= keystore
+
+include $(BUILD_EXECUTABLE)
+
+endif # !simulator))
diff --git a/cmds/keystore/commands.c b/cmds/keystore/commands.c
new file mode 100644
index 0000000..7474d81
--- /dev/null
+++ b/cmds/keystore/commands.c
@@ -0,0 +1,141 @@
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "keystore.h"
+
+static DIR *open_keystore(const char *dir)
+{
+ DIR *d;
+ if ((d = opendir(dir)) == NULL) {
+ if (mkdir(dir, 0770) < 0) {
+ LOGE("cannot create dir '%s': %s\n", dir, strerror(errno));
+ unlink(dir);
+ return NULL;
+ }
+ d = open_keystore(dir);
+ }
+ return d;
+}
+
+static int list_files(const char *dir, char reply[REPLY_MAX]) {
+ struct dirent *de;
+ DIR *d;
+
+ if ((d = open_keystore(dir)) == NULL) {
+ return -1;
+ }
+ reply[0]=0;
+ while ((de = readdir(d))) {
+ if (de->d_type != DT_REG) continue;
+ strlcat(reply, " ", REPLY_MAX);
+ if (strlcat(reply, de->d_name, REPLY_MAX) >= REPLY_MAX) {
+ LOGE("reply is too long(too many files under '%s'\n", dir);
+ return -1;
+ }
+ }
+ closedir(d);
+ return 0;
+}
+
+static int copy_keyfile(const char *keystore, const char *srcfile) {
+ int srcfd, dstfd;
+ int length;
+ char buf[2048];
+ char dstfile[KEYNAME_LENGTH];
+ const char *filename = strrchr(srcfile, '/');
+
+ strlcpy(dstfile, keystore, KEYNAME_LENGTH);
+ strlcat(dstfile, "/", KEYNAME_LENGTH);
+ if (strlcat(dstfile, filename ? filename + 1 : srcfile,
+ KEYNAME_LENGTH) >= KEYNAME_LENGTH) {
+ LOGE("keyname is too long '%s'\n", srcfile);
+ return -1;
+ }
+
+ if ((srcfd = open(srcfile, O_RDONLY)) == -1) {
+ LOGE("Cannot open the original file '%s'\n", srcfile);
+ return -1;
+ }
+ if ((dstfd = open(dstfile, O_CREAT|O_RDWR)) == -1) {
+ LOGE("Cannot open the destination file '%s'\n", dstfile);
+ return -1;
+ }
+ while((length = read(srcfd, buf, 2048)) > 0) {
+ write(dstfd, buf, length);
+ }
+ close(srcfd);
+ close(dstfd);
+ chmod(dstfile, 0440);
+ return 0;
+}
+
+static int install_key(const char *dir, const char *keyfile)
+{
+ struct dirent *de;
+ DIR *d;
+
+ if ((d = open_keystore(dir)) == NULL) {
+ return -1;
+ }
+ return copy_keyfile(dir, keyfile);
+}
+
+static int remove_key(const char *dir, const char *keyfile)
+{
+ char dstfile[KEYNAME_LENGTH];
+
+ strlcpy(dstfile, dir, KEYNAME_LENGTH);
+ strlcat(dstfile, "/", KEYNAME_LENGTH);
+ if (strlcat(dstfile, keyfile, KEYNAME_LENGTH) >= KEYNAME_LENGTH) {
+ LOGE("keyname is too long '%s'\n", keyfile);
+ return -1;
+ }
+ if (unlink(dstfile)) {
+ LOGE("cannot delete '%s': %s\n", dstfile, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+int list_certs(char reply[REPLY_MAX])
+{
+ return list_files(CERTS_DIR, reply);
+}
+
+int list_userkeys(char reply[REPLY_MAX])
+{
+ return list_files(USERKEYS_DIR, reply);
+}
+
+int install_cert(const char *certfile)
+{
+ return install_key(CERTS_DIR, certfile);
+}
+
+int install_userkey(const char *keyfile)
+{
+ return install_key(USERKEYS_DIR, keyfile);
+}
+
+int remove_cert(const char *certfile)
+{
+ return remove_key(CERTS_DIR, certfile);
+}
+
+int remove_userkey(const char *keyfile)
+{
+ return remove_key(USERKEYS_DIR, keyfile);
+}
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
new file mode 100644
index 0000000..dbb62b3
--- /dev/null
+++ b/cmds/keystore/keystore.c
@@ -0,0 +1,248 @@
+/*
+** Copyright 2009, 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 "keystore.h"
+
+
+static int do_list_certs(char **arg, char reply[REPLY_MAX])
+{
+ return list_certs(reply);
+}
+
+static int do_list_userkeys(char **arg, char reply[REPLY_MAX])
+{
+ return list_userkeys(reply);
+}
+
+static int do_install_cert(char **arg, char reply[REPLY_MAX])
+{
+ return install_cert(arg[0]); /* move the certificate to keystore */
+}
+
+static int do_remove_cert(char **arg, char reply[REPLY_MAX])
+{
+ return remove_cert(arg[0]); /* certificate */
+}
+
+static int do_install_userkey(char **arg, char reply[REPLY_MAX])
+{
+ return install_userkey(arg[0]); /* move the certificate to keystore */
+}
+
+static int do_remove_userkey(char **arg, char reply[REPLY_MAX])
+{
+ return remove_userkey(arg[0]); /* userkey */
+}
+
+struct cmdinfo {
+ const char *name;
+ unsigned numargs;
+ int (*func)(char **arg, char reply[REPLY_MAX]);
+};
+
+
+struct cmdinfo cmds[] = {
+ { "listcerts", 0, do_list_certs },
+ { "listuserkeys", 0, do_list_userkeys },
+ { "installcert", 1, do_install_cert },
+ { "removecert", 1, do_remove_cert },
+ { "installuserkey", 1, do_install_userkey },
+ { "removeuserkey", 1, do_remove_userkey },
+};
+
+static int readx(int s, void *_buf, int count)
+{
+ char *buf = _buf;
+ int n = 0, r;
+ if (count < 0) return -1;
+ while (n < count) {
+ r = read(s, buf + n, count - n);
+ if (r < 0) {
+ if (errno == EINTR) continue;
+ LOGE("read error: %s\n", strerror(errno));
+ return -1;
+ }
+ if (r == 0) {
+ LOGE("eof\n");
+ return -1; /* EOF */
+ }
+ n += r;
+ }
+ return 0;
+}
+
+static int writex(int s, const void *_buf, int count)
+{
+ const char *buf = _buf;
+ int n = 0, r;
+ if (count < 0) return -1;
+ while (n < count) {
+ r = write(s, buf + n, count - n);
+ if (r < 0) {
+ if (errno == EINTR) continue;
+ LOGE("write error: %s\n", strerror(errno));
+ return -1;
+ }
+ n += r;
+ }
+ return 0;
+}
+
+
+/* Tokenize the command buffer, locate a matching command,
+ * ensure that the required number of arguments are provided,
+ * call the function(), return the result.
+ */
+static int execute(int s, char cmd[BUFFER_MAX])
+{
+ char reply[REPLY_MAX];
+ char *arg[TOKEN_MAX+1];
+ unsigned i;
+ unsigned n = 0;
+ unsigned short count;
+ int ret = -1;
+
+ /* default reply is "" */
+ reply[0] = 0;
+
+ /* n is number of args (not counting arg[0]) */
+ arg[0] = cmd;
+ while (*cmd) {
+ if (isspace(*cmd)) {
+ *cmd++ = 0;
+ n++;
+ arg[n] = cmd;
+ if (n == TOKEN_MAX) {
+ LOGE("too many arguments\n");
+ goto done;
+ }
+ }
+ cmd++;
+ }
+
+ for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
+ if (!strcmp(cmds[i].name,arg[0])) {
+ if (n != cmds[i].numargs) {
+ LOGE("%s requires %d arguments (%d given)\n",
+ cmds[i].name, cmds[i].numargs, n);
+ } else {
+ ret = cmds[i].func(arg + 1, reply);
+ }
+ goto done;
+ }
+ }
+ LOGE("unsupported command '%s'\n", arg[0]);
+
+done:
+ if (reply[0]) {
+ n = snprintf(cmd, BUFFER_MAX, "%d %s", ret, reply);
+ } else {
+ n = snprintf(cmd, BUFFER_MAX, "%d", ret);
+ }
+ if (n > BUFFER_MAX) n = BUFFER_MAX;
+ count = n;
+
+ if (writex(s, &count, sizeof(count))) return -1;
+ if (writex(s, cmd, count)) return -1;
+
+ return 0;
+}
+
+int shell_command(const int argc, const char **argv)
+{
+ int fd, i, r;
+ unsigned short count;
+ char cmd[BUFFER_MAX]="";
+
+ fd = socket_local_client(SOCKET_PATH,
+ ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_STREAM);
+ if (fd == -1) {
+ fprintf(stderr, "Keystore service is not up and running\n");
+ exit(1);
+ }
+ for(i = 0; i < argc; i++) {
+ if (i > 0) strlcat(cmd, " ", BUFFER_MAX);
+ if(strlcat(cmd, argv[i], BUFFER_MAX) >= BUFFER_MAX) {
+ fprintf(stderr, "Arguments are too long\n");
+ exit(1);
+ }
+ }
+ count = strlen(cmd);
+ if (writex(fd, &count, sizeof(count))) return -1;
+ if (writex(fd, cmd, strlen(cmd))) return -1;
+ if (readx(fd, &count, sizeof(count))) return -1;
+ if (readx(fd, cmd, count)) return -1;
+ cmd[count]=0;
+ fprintf(stdout, "%s\n", cmd);
+ return 0;
+}
+
+int main(const int argc, const char *argv[])
+{
+ char buf[BUFFER_MAX];
+ struct sockaddr addr;
+ socklen_t alen;
+ int lsocket, s, count;
+
+ if (argc > 1) {
+ return shell_command(argc - 1, argv + 1);
+ }
+
+ lsocket = android_get_control_socket(SOCKET_PATH);
+ if (lsocket < 0) {
+ LOGE("Failed to get socket from environment: %s\n", strerror(errno));
+ exit(1);
+ }
+ if (listen(lsocket, 5)) {
+ LOGE("Listen on socket failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ fcntl(lsocket, F_SETFD, FD_CLOEXEC);
+
+ for (;;) {
+ alen = sizeof(addr);
+ s = accept(lsocket, &addr, &alen);
+ if (s < 0) {
+ LOGE("Accept failed: %s\n", strerror(errno));
+ continue;
+ }
+ fcntl(s, F_SETFD, FD_CLOEXEC);
+
+ LOGI("new connection\n");
+ for (;;) {
+ unsigned short count;
+ if (readx(s, &count, sizeof(count))) {
+ LOGE("failed to read size\n");
+ break;
+ }
+ if ((count < 1) || (count >= BUFFER_MAX)) {
+ LOGE("invalid size %d\n", count);
+ break;
+ }
+ if (readx(s, buf, count)) {
+ LOGE("failed to read command\n");
+ break;
+ }
+ buf[count] = 0;
+ if (execute(s, buf)) break;
+ }
+ LOGI("closing connection\n");
+ close(s);
+ }
+
+ return 0;
+}
diff --git a/cmds/keystore/keystore.h b/cmds/keystore/keystore.h
new file mode 100644
index 0000000..35acf0b
--- /dev/null
+++ b/cmds/keystore/keystore.h
@@ -0,0 +1,57 @@
+/*
+**
+** Copyright 2009, 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 "keystore"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <utime.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <cutils/sockets.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#define SOCKET_PATH "keystore"
+
+
+/* path of the keystore */
+
+#define KEYSTORE_DIR_PREFIX "/data/misc/keystore"
+#define CERTS_DIR KEYSTORE_DIR_PREFIX "/certs"
+#define USERKEYS_DIR KEYSTORE_DIR_PREFIX "/userkeys"
+
+#define BUFFER_MAX 1024 /* input buffer for commands */
+#define TOKEN_MAX 8 /* max number of arguments in buffer */
+#define REPLY_MAX 1024 /* largest reply allowed */
+#define KEYNAME_LENGTH 128
+
+/* commands.c */
+int list_certs(char reply[REPLY_MAX]);
+int list_userkeys(char reply[REPLY_MAX]);
+int install_cert(const char *certfile);
+int install_userkey(const char *keyfile);
+int remove_cert(const char *certfile);
+int remove_userkey(const char *keyfile);
diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h
index 06f3820..d2aa30e 100644
--- a/include/tts/TtsEngine.h
+++ b/include/tts/TtsEngine.h
@@ -16,33 +16,45 @@
#include <media/AudioSystem.h>
// This header defines the interface used by the Android platform
-// to access Text-To-Speech functionality in shared libraries that implement speech
-// synthesis and the management of resources associated with the synthesis.
-// An example of the implementation of this interface can be found in
+// to access Text-To-Speech functionality in shared libraries that implement
+// speech synthesis and the management of resources associated with the
+// synthesis.
+// An example of the implementation of this interface can be found in
// FIXME: add path+name to implementation of default TTS engine
// Libraries implementing this interface are used in:
// frameworks/base/tts/jni/android_tts_SpeechSynthesis.cpp
namespace android {
+enum tts_synth_status {
+ TTS_SYNTH_DONE = 0,
+ TTS_SYNTH_PENDING = 1
+};
+
+enum tts_callback_status {
+ TTS_CALLBACK_HALT = 0,
+ TTS_CALLBACK_CONTINUE = 1
+};
+
// The callback is used by the implementation of this interface to notify its
// client, the Android TTS service, that the last requested synthesis has been
-// completed.
+// completed. // TODO reword
// The callback for synthesis completed takes:
-// void * - The userdata pointer set in the original synth call
-// uint32_t - Track sampling rate in Hz
-// audio_format - The AudioSystem::audio_format enum
-// int - The number of channels
-// int8_t * - A buffer of audio data only valid during the execution of the callback
-// size_t - The size of the buffer
-// Note about memory management:
-// The implementation of TtsEngine is responsible for the management of the memory
-// it allocates to store the synthesized speech. After the execution of the callback
-// to hand the synthesized data to the client of TtsEngine, the TTS engine is
-// free to reuse or free the previously allocated memory.
-// This implies that the implementation of the "synthDoneCB" callback cannot use
-// the pointer to the buffer of audio samples outside of the callback itself.
-typedef void (synthDoneCB_t)(void *, uint32_t, AudioSystem::audio_format, int, int8_t *, size_t);
+// @param [inout] void *& - The userdata pointer set in the original
+// synth call
+// @param [in] uint32_t - Track sampling rate in Hz
+// @param [in] audio_format - The AudioSystem::audio_format enum
+// @param [in] int - The number of channels
+// @param [inout] int8_t *& - A buffer of audio data only valid during the
+// execution of the callback
+// @param [inout] size_t & - The size of the buffer
+// @param [in] tts_synth_status - indicate whether the synthesis is done, or
+// if more data is to be synthesized.
+// @return TTS_CALLBACK_HALT to indicate the synthesis must stop,
+// TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if
+// there is more data to produce.
+typedef tts_callback_status (synthDoneCB_t)(void *&, uint32_t,
+ AudioSystem::audio_format, int, int8_t *&, size_t&, tts_synth_status);
class TtsEngine;
extern "C" TtsEngine* getTtsEngine();
@@ -53,7 +65,8 @@
TTS_FEATURE_UNSUPPORTED = -2,
TTS_VALUE_INVALID = -3,
TTS_PROPERTY_UNSUPPORTED = -4,
- TTS_PROPERTY_SIZE_TOO_SMALL = -5
+ TTS_PROPERTY_SIZE_TOO_SMALL = -5,
+ TTS_MISSING_RESOURCES = -6
};
class TtsEngine
@@ -68,38 +81,41 @@
// @return TTS_SUCCESS, or TTS_FAILURE
virtual tts_result shutdown();
- // Interrupt synthesis and flushes any synthesized data that hasn't been output yet.
- // This will block until callbacks underway are completed.
+ // Interrupt synthesis and flushes any synthesized data that hasn't been
+ // output yet. This will block until callbacks underway are completed.
// @return TTS_SUCCESS, or TTS_FAILURE
virtual tts_result stop();
- // Load the resources associated with the specified language. The loaded language will
- // only be used once a call to setLanguage() with the same language value is issued.
- // Language values are based on the Android conventions for localization as described in
- // the Android platform documentation on internationalization. This implies that language
- // data is specified in the format xx-rYY, where xx is a two letter ISO 639-1 language code
- // in lowercase and rYY is a two letter ISO 3166-1-alpha-2 language code in uppercase
- // preceded by a lowercase "r".
+ // Load the resources associated with the specified language. The loaded
+ // language will only be used once a call to setLanguage() with the same
+ // language value is issued. Language values are based on the Android
+ // conventions for localization as described in the Android platform
+ // documentation on internationalization. This implies that language
+ // data is specified in the format xx-rYY, where xx is a two letter
+ // ISO 639-1 language code in lowercase and rYY is a two letter
+ // ISO 3166-1-alpha-2 language code in uppercase preceded by a
+ // lowercase "r".
// @param value pointer to the language value
// @param size length of the language value
// @return TTS_SUCCESS, or TTS_FAILURE
virtual tts_result loadLanguage(const char *value, const size_t size);
- // Signal the engine to use the specified language. This will force the language to be
- // loaded if it wasn't loaded previously with loadLanguage().
+ // Signal the engine to use the specified language. This will force the
+ // language to be loaded if it wasn't loaded previously with loadLanguage().
// See loadLanguage for the specification of the language.
// @param value pointer to the language value
// @param size length of the language value
// @return TTS_SUCCESS, or TTS_FAILURE
virtual tts_result setLanguage(const char *value, const size_t size);
- // Retrieve the currently set language, or an empty "value" if no language has
- // been set.
+ // Retrieve the currently set language, or an empty "value" if no language
+ // has been set.
// @param[out] value pointer to the retrieved language value
- // @param[inout] iosize in: stores the size available to store the language value in *value
- // out: stores the size required to hold the language value if
- // getLanguage() returned TTS_PROPERTY_SIZE_TOO_SMALL,
- // unchanged otherwise.
+ // @param[inout] iosize in: stores the size available to store the language
+ // value in *value
+ // out: stores the size required to hold the language
+ // value if getLanguage() returned
+ // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise.
// @return TTS_SUCCESS, or TTS_PROPERTY_SIZE_TOO_SMALL, or TTS_FAILURE
virtual tts_result getLanguage(char *value, size_t *iosize);
@@ -108,23 +124,31 @@
// @param property pointer to the property name
// @param value pointer to the property value
// @param size maximum size required to store this type of property
- // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE,
+ // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE,
// or TTS_VALUE_INVALID
- virtual tts_result setProperty(const char *property, const char *value, const size_t size);
+ virtual tts_result setProperty(const char *property, const char *value,
+ const size_t size);
// Retrieve a property from the TTS engine
// @param property pointer to the property name
// @param[out] value pointer to the retrieved language value
- // @param[inout] iosize in: stores the size available to store the property value
- // out: stores the size required to hold the language value if
- // getLanguage() returned TTS_PROPERTY_SIZE_TOO_SMALL,
- // unchanged otherwise.
- // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_PROPERTY_SIZE_TOO_SMALL
- virtual tts_result getProperty(const char *property, char *value, size_t *iosize);
+ // @param[inout] iosize in: stores the size available to store the
+ // property value.
+ // out: stores the size required to hold the language
+ // value if getLanguage() returned
+ // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise
+ // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS,
+ // or TTS_PROPERTY_SIZE_TOO_SMALL
+ virtual tts_result getProperty(const char *property, char *value,
+ size_t *iosize);
// Synthesize the text.
- // When synthesis completes, the engine invokes the callback to notify the TTS framework.
- // Note about the format of the input: the text parameter may use the following elements
+ // As the synthesis is performed, the engine invokes the callback to notify
+ // the TTS framework that it has filled the given buffer, and indicates how
+ // many bytes it wrote. The callback is called repeatedly until the engine
+ // has generated all the audio data corresponding to the text.
+ // Note about the format of the input: the text parameter may use the
+ // following elements
// and their respective attributes as defined in the SSML 1.0 specification:
// * lang
// * say-as:
@@ -153,14 +177,25 @@
// Text is coded in UTF-8.
// @param text the UTF-8 text to synthesize
// @param userdata pointer to be returned when the call is invoked
+ // @param buffer the location where the synthesized data must be written
+ // @param bufferSize the number of bytes that can be written in buffer
// @return TTS_SUCCESS or TTS_FAILURE
- virtual tts_result synthesizeText(const char *text, void *userdata);
+ virtual tts_result synthesizeText(const char *text, int8_t *buffer,
+ size_t bufferSize, void *userdata);
- // Synthesize IPA text. When synthesis completes, the engine must call the given callback to notify the TTS API.
+ // Synthesize IPA text.
+ // As the synthesis is performed, the engine invokes the callback to notify
+ // the TTS framework that it has filled the given buffer, and indicates how
+ // many bytes it wrote. The callback is called repeatedly until the engine
+ // has generated all the audio data corresponding to the IPA data.
// @param ipa the IPA data to synthesize
// @param userdata pointer to be returned when the call is invoked
- // @return TTS_FEATURE_UNSUPPORTED if IPA is not supported, otherwise TTS_SUCCESS or TTS_FAILURE
- virtual tts_result synthesizeIpa(const char *ipa, void *userdata);
+ // @param buffer the location where the synthesized data must be written
+ // @param bufferSize the number of bytes that can be written in buffer
+ // @return TTS_FEATURE_UNSUPPORTED if IPA is not supported,
+ // otherwise TTS_SUCCESS or TTS_FAILURE
+ virtual tts_result synthesizeIpa(const char *ipa, int8_t *buffer,
+ size_t bufferSize, void *userdata);
};
} // namespace android
diff --git a/include/ui/Point.h b/include/ui/Point.h
index dbbad1e..1653120 100644
--- a/include/ui/Point.h
+++ b/include/ui/Point.h
@@ -31,12 +31,9 @@
// because we want the compiler generated versions
// Default constructor doesn't initialize the Point
- inline Point()
- {
+ inline Point() {
}
-
- inline Point(int _x, int _y) : x(_x), y(_y)
- {
+ inline Point(int x, int y) : x(x), y(y) {
}
inline bool operator == (const Point& rhs) const {
@@ -57,8 +54,8 @@
}
inline Point& operator - () {
- x=-x;
- y=-y;
+ x = -x;
+ y = -y;
return *this;
}
@@ -73,11 +70,13 @@
return *this;
}
- Point operator + (const Point& rhs) const {
- return Point(x+rhs.x, y+rhs.y);
+ const Point operator + (const Point& rhs) const {
+ const Point result(x+rhs.x, y+rhs.y);
+ return result;
}
- Point operator - (const Point& rhs) const {
- return Point(x-rhs.x, y-rhs.y);
+ const Point operator - (const Point& rhs) const {
+ const Point result(x-rhs.x, y-rhs.y);
+ return result;
}
};
diff --git a/include/ui/Rect.h b/include/ui/Rect.h
index d232847..da72944 100644
--- a/include/ui/Rect.h
+++ b/include/ui/Rect.h
@@ -33,23 +33,16 @@
// we don't provide copy-ctor and operator= on purpose
// because we want the compiler generated versions
- inline Rect()
- {
+ inline Rect() {
}
-
inline Rect(int w, int h)
- : left(0), top(0), right(w), bottom(h)
- {
+ : left(0), top(0), right(w), bottom(h) {
}
-
inline Rect(int l, int t, int r, int b)
- : left(l), top(t), right(r), bottom(b)
- {
+ : left(l), top(t), right(r), bottom(b) {
}
-
inline Rect(const Point& lt, const Point& rb)
- : left(lt.x), top(lt.y), right(rb.x), bottom(rb.y)
- {
+ : left(lt.x), top(lt.y), right(rb.x), bottom(rb.y) {
}
void makeInvalid();
@@ -78,21 +71,22 @@
return bottom-top;
}
- // returns left-top Point non-const reference, can be assigned
- inline Point& leftTop() {
- return reinterpret_cast<Point&>(left);
+ void setLeftTop(const Point& lt) {
+ left = lt.x;
+ top = lt.y;
}
- // returns right bottom non-const reference, can be assigned
- inline Point& rightBottom() {
- return reinterpret_cast<Point&>(right);
+
+ void setRightBottom(const Point& rb) {
+ right = rb.x;
+ bottom = rb.y;
}
// the following 4 functions return the 4 corners of the rect as Point
- inline const Point& leftTop() const {
- return reinterpret_cast<const Point&>(left);
+ Point leftTop() const {
+ return Point(left, top);
}
- inline const Point& rightBottom() const {
- return reinterpret_cast<const Point&>(right);
+ Point rightBottom() const {
+ return Point(right, bottom);
}
Point rightTop() const {
return Point(right, top);
@@ -133,8 +127,8 @@
Rect& operator -= (const Point& rhs) {
return offsetBy(-rhs.x, -rhs.y);
}
- Rect operator + (const Point& rhs) const;
- Rect operator - (const Point& rhs) const;
+ const Rect operator + (const Point& rhs) const;
+ const Rect operator - (const Point& rhs) const;
void translate(int dx, int dy) { // legacy, don't use.
offsetBy(dx, dy);
diff --git a/include/utils/backup_helpers.h b/include/utils/BackupHelpers.h
similarity index 100%
rename from include/utils/backup_helpers.h
rename to include/utils/BackupHelpers.h
diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp
index e4f4aad..324111b 100644
--- a/libs/audioflinger/AudioFlinger.cpp
+++ b/libs/audioflinger/AudioFlinger.cpp
@@ -817,19 +817,22 @@
{
AutoMutex lock(mHardwareLock);
if (mForcedSpeakerCount++ == 0) {
- mRouteRestoreTime = 0;
- mMusicMuteSaved = mHardwareMixerThread->streamMute(AudioSystem::MUSIC);
- if (mForcedRoute == 0 && !(mSavedRoute & AudioSystem::ROUTE_SPEAKER)) {
- LOGV("Route forced to Speaker ON %08x", mSavedRoute | AudioSystem::ROUTE_SPEAKER);
- mHardwareMixerThread->setStreamMute(AudioSystem::MUSIC, true);
- usleep(mHardwareMixerThread->latency()*1000);
- mHardwareStatus = AUDIO_HW_SET_ROUTING;
- mAudioHardware->setRouting(AudioSystem::MODE_NORMAL, mSavedRoute | AudioSystem::ROUTE_SPEAKER);
- mHardwareStatus = AUDIO_HW_IDLE;
- // delay track start so that audio hardware has time to siwtch routes
- usleep(kStartSleepTime);
+ if (mForcedRoute == 0) {
+ mMusicMuteSaved = mHardwareMixerThread->streamMute(AudioSystem::MUSIC);
+ LOGV("++mForcedSpeakerCount == 0, mMusicMuteSaved = %d, mRouteRestoreTime = %d", mMusicMuteSaved, mRouteRestoreTime);
+ if (!(mSavedRoute & AudioSystem::ROUTE_SPEAKER)) {
+ LOGV("Route forced to Speaker ON %08x", mSavedRoute | AudioSystem::ROUTE_SPEAKER);
+ mHardwareMixerThread->setStreamMute(AudioSystem::MUSIC, true);
+ usleep(mHardwareMixerThread->latency()*1000);
+ mHardwareStatus = AUDIO_HW_SET_ROUTING;
+ mAudioHardware->setRouting(AudioSystem::MODE_NORMAL, mSavedRoute | AudioSystem::ROUTE_SPEAKER);
+ mHardwareStatus = AUDIO_HW_IDLE;
+ // delay track start so that audio hardware has time to siwtch routes
+ usleep(kStartSleepTime);
+ }
}
mForcedRoute = AudioSystem::ROUTE_SPEAKER;
+ mRouteRestoreTime = 0;
}
LOGV("mForcedSpeakerCount incremented to %d", mForcedSpeakerCount);
}
@@ -1553,7 +1556,6 @@
AudioFlinger::MixerThread::TrackBase::TrackBase(
const sp<MixerThread>& mixerThread,
const sp<Client>& client,
- int streamType,
uint32_t sampleRate,
int format,
int channelCount,
@@ -1563,7 +1565,6 @@
: RefBase(),
mMixerThread(mixerThread),
mClient(client),
- mStreamType(streamType),
mFrameCount(0),
mState(IDLE),
mClientTid(-1),
@@ -1713,12 +1714,13 @@
int channelCount,
int frameCount,
const sp<IMemory>& sharedBuffer)
- : TrackBase(mixerThread, client, streamType, sampleRate, format, channelCount, frameCount, 0, sharedBuffer)
+ : TrackBase(mixerThread, client, sampleRate, format, channelCount, frameCount, 0, sharedBuffer)
{
mVolume[0] = 1.0f;
mVolume[1] = 1.0f;
mMute = false;
mSharedBuffer = sharedBuffer;
+ mStreamType = streamType;
}
AudioFlinger::MixerThread::Track::~Track()
@@ -1902,15 +1904,15 @@
AudioFlinger::MixerThread::RecordTrack::RecordTrack(
const sp<MixerThread>& mixerThread,
const sp<Client>& client,
- int streamType,
+ int inputSource,
uint32_t sampleRate,
int format,
int channelCount,
int frameCount,
uint32_t flags)
- : TrackBase(mixerThread, client, streamType, sampleRate, format,
+ : TrackBase(mixerThread, client, sampleRate, format,
channelCount, frameCount, flags, 0),
- mOverflow(false)
+ mOverflow(false), mInputSource(inputSource)
{
}
@@ -2235,7 +2237,7 @@
sp<IAudioRecord> AudioFlinger::openRecord(
pid_t pid,
- int streamType,
+ int inputSource,
uint32_t sampleRate,
int format,
int channelCount,
@@ -2258,7 +2260,7 @@
goto Exit;
}
- if (uint32_t(streamType) >= AudioRecord::NUM_STREAM_TYPES) {
+ if (uint32_t(inputSource) >= AudioRecord::NUM_INPUT_SOURCES) {
LOGE("invalid stream type");
lStatus = BAD_VALUE;
goto Exit;
@@ -2301,7 +2303,7 @@
frameCount = ((frameCount - 1)/inFrameCount + 1) * inFrameCount;
// create new record track. The record track uses one track in mHardwareMixerThread by convention.
- recordTrack = new MixerThread::RecordTrack(mHardwareMixerThread, client, streamType, sampleRate,
+ recordTrack = new MixerThread::RecordTrack(mHardwareMixerThread, client, inputSource, sampleRate,
format, channelCount, frameCount, flags);
}
if (recordTrack->getCblk() == NULL) {
@@ -2408,7 +2410,7 @@
LOGV("AudioRecordThread: loop starting");
if (mRecordTrack != 0) {
input = mAudioHardware->openInputStream(
- mRecordTrack->type(),
+ mRecordTrack->inputSource(),
mRecordTrack->format(),
mRecordTrack->channelCount(),
mRecordTrack->sampleRate(),
diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h
index c7ca9ec..8e47b29 100644
--- a/libs/audioflinger/AudioFlinger.h
+++ b/libs/audioflinger/AudioFlinger.h
@@ -139,7 +139,7 @@
// record interface
virtual sp<IAudioRecord> openRecord(
pid_t pid,
- int streamType,
+ int inputSource,
uint32_t sampleRate,
int format,
int channelCount,
@@ -232,7 +232,6 @@
TrackBase(const sp<MixerThread>& mixerThread,
const sp<Client>& client,
- int streamType,
uint32_t sampleRate,
int format,
int channelCount,
@@ -260,10 +259,6 @@
return mCblk;
}
- int type() const {
- return mStreamType;
- }
-
int format() const {
return mFormat;
}
@@ -293,7 +288,6 @@
sp<Client> mClient;
sp<IMemory> mCblkMemory;
audio_track_cblk_t* mCblk;
- int mStreamType;
void* mBuffer;
void* mBufferEnd;
uint32_t mFrameCount;
@@ -328,6 +322,11 @@
void mute(bool);
void setVolume(float left, float right);
+ int type() const {
+ return mStreamType;
+ }
+
+
protected:
friend class MixerThread;
friend class AudioFlinger;
@@ -364,6 +363,7 @@
int8_t mRetryCount;
sp<IMemory> mSharedBuffer;
bool mResetDone;
+ int mStreamType;
}; // end of Track
// record track
@@ -371,7 +371,7 @@
public:
RecordTrack(const sp<MixerThread>& mixerThread,
const sp<Client>& client,
- int streamType,
+ int inputSource,
uint32_t sampleRate,
int format,
int channelCount,
@@ -385,6 +385,8 @@
bool overflow() { bool tmp = mOverflow; mOverflow = false; return tmp; }
bool setOverflow() { bool tmp = mOverflow; mOverflow = true; return tmp; }
+ int inputSource() const { return mInputSource; }
+
private:
friend class AudioFlinger;
friend class AudioFlinger::RecordHandle;
@@ -397,6 +399,7 @@
virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
bool mOverflow;
+ int mInputSource;
};
// playback track
diff --git a/libs/audioflinger/AudioHardwareGeneric.cpp b/libs/audioflinger/AudioHardwareGeneric.cpp
index a97c0bc..1e159b8 100644
--- a/libs/audioflinger/AudioHardwareGeneric.cpp
+++ b/libs/audioflinger/AudioHardwareGeneric.cpp
@@ -98,8 +98,8 @@
status_t *status, AudioSystem::audio_in_acoustics acoustics)
{
// check for valid input source
- if ((inputSource != AudioRecord::DEFAULT_INPUT) &&
- (inputSource != AudioRecord::MIC_INPUT)) {
+ if ((inputSource < AudioRecord::DEFAULT_INPUT) ||
+ (inputSource >= AudioRecord::NUM_INPUT_SOURCES)) {
return 0;
}
diff --git a/libs/audioflinger/AudioHardwareStub.cpp b/libs/audioflinger/AudioHardwareStub.cpp
index c61e6e6..0ab4c60 100644
--- a/libs/audioflinger/AudioHardwareStub.cpp
+++ b/libs/audioflinger/AudioHardwareStub.cpp
@@ -61,8 +61,8 @@
status_t *status, AudioSystem::audio_in_acoustics acoustics)
{
// check for valid input source
- if ((inputSource != AudioRecord::DEFAULT_INPUT) &&
- (inputSource != AudioRecord::MIC_INPUT)) {
+ if ((inputSource < AudioRecord::DEFAULT_INPUT) ||
+ (inputSource >= AudioRecord::NUM_INPUT_SOURCES)) {
return 0;
}
diff --git a/libs/surfaceflinger/LayerBitmap.cpp b/libs/surfaceflinger/LayerBitmap.cpp
index e844350..397ddc8 100644
--- a/libs/surfaceflinger/LayerBitmap.cpp
+++ b/libs/surfaceflinger/LayerBitmap.cpp
@@ -114,7 +114,9 @@
}
if (mBitsMemory==0 || mSurface.data==0) {
- LOGE("not enough memory for layer bitmap size=%u", size);
+ LOGE("not enough memory for layer bitmap "
+ "size=%u (w=%d, h=%d, stride=%d, format=%d)",
+ size, int(w), int(h), int(stride), int(format));
allocator->dump("LayerBitmap");
mSurface.data = 0;
mSize = -1U;
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index f944357..b3b2104 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -20,7 +20,6 @@
LayerState.cpp \
Overlay.cpp \
PixelFormat.cpp \
- Point.cpp \
Rect.cpp \
Region.cpp \
Surface.cpp \
diff --git a/libs/ui/Point.cpp b/libs/ui/Point.cpp
deleted file mode 100644
index 438d49f..0000000
--- a/libs/ui/Point.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Point.cpp
- * Android
- *
- * Created on 11/16/2006.
- * Copyright 2005 The Android Open Source Project
- *
- */
-
-#include <ui/Point.h>
-
diff --git a/libs/ui/Rect.cpp b/libs/ui/Rect.cpp
index 99e68bb..66b9576 100644
--- a/libs/ui/Rect.cpp
+++ b/libs/ui/Rect.cpp
@@ -1,21 +1,28 @@
/*
- * Rect.cpp
- * Android
+ * Copyright (C) 2009 The Android Open Source Project
*
- * Created on 10/14/05.
- * Copyright 2005 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 <ui/Rect.h>
namespace android {
-inline int min(int a, int b) {
+static inline int min(int a, int b) {
return (a<b) ? a : b;
}
-inline int max(int a, int b) {
+static inline int max(int a, int b) {
return (a>b) ? a : b;
}
@@ -64,14 +71,16 @@
return *this;
}
-Rect Rect::operator + (const Point& rhs) const
+const Rect Rect::operator + (const Point& rhs) const
{
- return Rect(left+rhs.x, top+rhs.y, right+rhs.x, bottom+rhs.y);
+ const Rect result(left+rhs.x, top+rhs.y, right+rhs.x, bottom+rhs.y);
+ return result;
}
-Rect Rect::operator - (const Point& rhs) const
+const Rect Rect::operator - (const Point& rhs) const
{
- return Rect(left-rhs.x, top-rhs.y, right-rhs.x, bottom-rhs.y);
+ const Rect result(left-rhs.x, top-rhs.y, right-rhs.x, bottom-rhs.y);
+ return result;
}
bool Rect::intersect(const Rect& with, Rect* result) const
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 5a1a89b..9bdd64a 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -117,8 +117,8 @@
IPermissionController.cpp \
IServiceManager.cpp \
Unicode.cpp \
- backup_data.cpp \
- backup_helper_file.cpp
+ BackupData.cpp \
+ BackupHelpers.cpp
ifeq ($(TARGET_SIMULATOR),true)
LOCAL_SRC_FILES += $(hostSources)
diff --git a/libs/utils/backup_data.cpp b/libs/utils/BackupData.cpp
similarity index 99%
rename from libs/utils/backup_data.cpp
rename to libs/utils/BackupData.cpp
index 95c05b7..120f23d 100644
--- a/libs/utils/backup_data.cpp
+++ b/libs/utils/BackupData.cpp
@@ -16,7 +16,7 @@
#define LOG_TAG "backup_data"
-#include <utils/backup_helpers.h>
+#include <utils/BackupHelpers.h>
#include <utils/ByteOrder.h>
#include <stdio.h>
diff --git a/libs/utils/backup_helper_file.cpp b/libs/utils/BackupHelpers.cpp
similarity index 99%
rename from libs/utils/backup_helper_file.cpp
rename to libs/utils/BackupHelpers.cpp
index 7ec2ce8..7f423a8 100644
--- a/libs/utils/backup_helper_file.cpp
+++ b/libs/utils/BackupHelpers.cpp
@@ -16,7 +16,7 @@
#define LOG_TAG "file_backup_helper"
-#include <utils/backup_helpers.h>
+#include <utils/BackupHelpers.h>
#include <utils/KeyedVector.h>
#include <utils/ByteOrder.h>
@@ -350,10 +350,11 @@
FileState& g = newSnapshot.editValueAt(m);
int fd = open(realFilename.string(), O_RDONLY);
- if (fd != -1) {
+ if (fd < 0) {
// We can't open the file. Don't report it as a delete either. Let the
// server keep the old version. Maybe they'll be able to deal with it
// on restore.
+ LOGP("Unable to open file %s - skipping", realFilename.string());
} else {
g.crc32 = compute_crc32(fd);
diff --git a/libs/utils/characterData.h b/libs/utils/CharacterData.h
similarity index 100%
rename from libs/utils/characterData.h
rename to libs/utils/CharacterData.h
diff --git a/libs/utils/Unicode.cpp b/libs/utils/Unicode.cpp
index 33f535f..f92703e 100644
--- a/libs/utils/Unicode.cpp
+++ b/libs/utils/Unicode.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#include "utils/AndroidUnicode.h"
-#include "characterData.h"
+#include <utils/AndroidUnicode.h>
+#include "CharacterData.h"
#define LOG_TAG "Unicode"
-#include "utils/Log.h"
+#include <utils/Log.h>
// ICU headers for using macros
#include <unicode/utf16.h>
diff --git a/libs/utils/ZipEntry.cpp b/libs/utils/ZipEntry.cpp
index fbc9e67..96f9fc4 100644
--- a/libs/utils/ZipEntry.cpp
+++ b/libs/utils/ZipEntry.cpp
@@ -20,8 +20,8 @@
#define LOG_TAG "zip"
-#include "utils/ZipEntry.h"
-#include "utils/Log.h"
+#include <utils/ZipEntry.h>
+#include <utils/Log.h>
#include <stdio.h>
#include <string.h>
diff --git a/libs/utils/ZipFile.cpp b/libs/utils/ZipFile.cpp
index 89aa874..6f27d17 100644
--- a/libs/utils/ZipFile.cpp
+++ b/libs/utils/ZipFile.cpp
@@ -20,9 +20,9 @@
#define LOG_TAG "zip"
-#include "utils/ZipFile.h"
-#include "utils/ZipUtils.h"
-#include "utils/Log.h"
+#include <utils/ZipFile.h>
+#include <utils/ZipUtils.h>
+#include <utils/Log.h>
#include <zlib.h>
#define DEF_MEM_LEVEL 8 // normally in zutil.h?
diff --git a/libs/utils/ZipFileCRO.cpp b/libs/utils/ZipFileCRO.cpp
index d312daf..45f6c8b 100644
--- a/libs/utils/ZipFileCRO.cpp
+++ b/libs/utils/ZipFileCRO.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "utils/ZipFileCRO.h"
-#include "utils/ZipFileRO.h"
+#include <utils/ZipFileCRO.h>
+#include <utils/ZipFileRO.h>
using namespace android;
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index ae8c719..6c701dd 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -19,9 +19,9 @@
//
#define LOG_TAG "zipro"
//#define LOG_NDEBUG 0
-#include "utils/ZipFileRO.h"
-#include "utils/Log.h"
-#include "utils/misc.h"
+#include <utils/ZipFileRO.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
#include <zlib.h>
diff --git a/libs/utils/ZipUtils.cpp b/libs/utils/ZipUtils.cpp
index bfbacfe..5df94cb 100644
--- a/libs/utils/ZipUtils.cpp
+++ b/libs/utils/ZipUtils.cpp
@@ -20,9 +20,9 @@
#define LOG_TAG "ziputil"
-#include "utils/ZipUtils.h"
-#include "utils/ZipFileRO.h"
-#include "utils/Log.h"
+#include <utils/ZipUtils.h>
+#include <utils/ZipFileRO.h>
+#include <utils/Log.h>
#include <stdlib.h>
#include <string.h>
diff --git a/opengl/libagl/array.cpp b/opengl/libagl/array.cpp
index 8fa7566..3e9c6a5 100644
--- a/opengl/libagl/array.cpp
+++ b/opengl/libagl/array.cpp
@@ -951,6 +951,8 @@
v->index = first;
first &= vertex_cache_t::INDEX_MASK;
const GLubyte* vp = c->arrays.vertex.element(first);
+ v->obj.z = 0;
+ v->obj.w = 0x10000;
c->arrays.vertex.fetch(c, v->obj.v, vp);
c->arrays.mvp_transform(&c->transforms.mvp, &v->clip, &v->obj);
c->arrays.perspective(c, v);
@@ -966,6 +968,8 @@
do {
v->flags = 0;
v->index = first++;
+ v->obj.z = 0;
+ v->obj.w = 0x10000;
c->arrays.vertex.fetch(c, v->obj.v, vp);
c->arrays.mvp_transform(mvp, &v->clip, &v->obj);
c->arrays.perspective(c, v);
diff --git a/opengl/libagl/light.cpp b/opengl/libagl/light.cpp
index 25c41d0..8ae32cc 100644
--- a/opengl/libagl/light.cpp
+++ b/opengl/libagl/light.cpp
@@ -38,13 +38,14 @@
static void lightVertexMaterial(ogles_context_t* c, vertex_t* v);
static inline void vscale3(GLfixed* d, const GLfixed* m, GLfixed s);
-static inline void vsub3w(GLfixed* d, const GLfixed* a, const GLfixed* b);
static __attribute__((noinline))
void vnorm3(GLfixed* d, const GLfixed* a);
static inline void vsa3(GLfixed* d,
const GLfixed* m, GLfixed s, const GLfixed* a);
+static inline void vss3(GLfixed* d,
+ const GLfixed* m, GLfixed s, const GLfixed* a);
static inline void vmla3(GLfixed* d,
const GLfixed* m0, const GLfixed* m1, const GLfixed* a);
static inline void vmul3(GLfixed* d,
@@ -151,18 +152,10 @@
}
static inline
-void vsub3w(GLfixed* d, const GLfixed* a, const GLfixed* b) {
- const GLfixed wa = a[3];
- const GLfixed wb = b[3];
- if (ggl_likely(wa == wb)) {
- d[0] = a[0] - b[0];
- d[1] = a[1] - b[1];
- d[2] = a[2] - b[2];
- } else {
- d[0] = gglMulSubx(a[0], wb, gglMulx(b[0], wa));
- d[1] = gglMulSubx(a[1], wb, gglMulx(b[1], wa));
- d[2] = gglMulSubx(a[2], wb, gglMulx(b[2], wa));
- }
+void vss3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a) {
+ d[0] = gglMulSubx(m[0], s, a[0]);
+ d[1] = gglMulSubx(m[1], s, a[1]);
+ d[2] = gglMulSubx(m[2], s, a[2]);
}
static inline
@@ -227,7 +220,7 @@
const int i = 31 - gglClz(en);
en &= ~(1<<i);
light_t& l = c->lighting.lights[i];
- c->transforms.mvui.point3(&c->transforms.mvui,
+ c->transforms.mvui.point4(&c->transforms.mvui,
&l.objPosition, &l.position);
vnorm3(l.normalizedObjPosition.v, l.objPosition.v);
}
@@ -318,6 +311,11 @@
vmul3(l.implicitAmbient.v, material.ambient.v, l.ambient.v);
vmul3(l.implicitDiffuse.v, material.diffuse.v, l.diffuse.v);
vmul3(l.implicitSpecular.v, material.specular.v, l.specular.v);
+ // this is just a flag to tell if we have a specular component
+ l.implicitSpecular.v[3] =
+ l.implicitSpecular.r |
+ l.implicitSpecular.g |
+ l.implicitSpecular.b;
}
// emission and ambient for the whole scene
vmla3( c->lighting.implicitSceneEmissionAndAmbient.v,
@@ -343,7 +341,11 @@
vec4_t n;
c->arrays.normal.fetch(c, n.v,
c->arrays.normal.element(v->index & vertex_cache_t::INDEX_MASK));
- if (c->transforms.rescaleNormals == GL_NORMALIZE)
+
+ // TODO: right now we handle GL_RESCALE_NORMALS as if ti were
+ // GL_NORMALIZE. We could optimize this by scaling mvui
+ // appropriately instead.
+ if (c->transforms.rescaleNormals)
vnorm3(n.v, n.v);
const material_t& material = c->lighting.front;
@@ -360,7 +362,8 @@
// compute vertex-to-light vector
if (ggl_unlikely(l.position.w)) {
- vsub3w(d.v, l.objPosition.v, v->obj.v);
+ // lightPos/1.0 - vertex/vertex.w == lightPos*vertex.w - vertex
+ vss3(d.v, l.objPosition.v, v->obj.w, v->obj.v);
sqDist = dot3(d.v, d.v);
vscale3(d.v, d.v, gglSqrtRecipx(sqDist));
} else {
diff --git a/opengl/libagl/matrix.cpp b/opengl/libagl/matrix.cpp
index f175cda..0b68dc0 100644
--- a/opengl/libagl/matrix.cpp
+++ b/opengl/libagl/matrix.cpp
@@ -55,7 +55,7 @@
static void point2__generic(transform_t const*, vec4_t* c, vec4_t const* o);
static void point3__generic(transform_t const*, vec4_t* c, vec4_t const* o);
static void point4__generic(transform_t const*, vec4_t* c, vec4_t const* o);
-static void normal__generic(transform_t const*, vec4_t* c, vec4_t const* o);
+static void point4__mvui(transform_t const*, vec4_t* c, vec4_t const* o);
// ----------------------------------------------------------------------------
#if 0
@@ -209,7 +209,8 @@
{
flags = 0;
ops = OP_ALL;
- point3 = normal__generic;
+ point3 = point4__mvui;
+ point4 = point4__mvui;
}
void transform_t::dump(const char* what)
@@ -596,66 +597,19 @@
void transform_state_t::update_mvui()
{
+ GLfloat r[16];
const GLfloat* const mv = modelview.top().elements();
-
- /*
- When transforming normals, we can use the upper 3x3 matrix, see:
- http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node26.html
- */
- // Also note that:
- // l(obj) = tr(M).l(eye) for infinite light
- // l(obj) = inv(M).l(eye) for local light
-
- const uint32_t ops = modelview.top_ops() & ~OP_TRANSLATE;
- if (ggl_likely((!(ops & ~OP_ROTATE)) ||
- (rescaleNormals && modelview.isRigidBody()))) {
- // if the modelview matrix is a rigid body transformation
- // (translation, rotation, uniform scaling), then we can bypass
- // the inverse by transposing the matrix.
- GLfloat rescale = 1.0f;
- if (rescaleNormals == GL_RESCALE_NORMAL) {
- if (!(ops & ~OP_UNIFORM_SCALE)) {
- rescale = reciprocalf(mv[I(0,0)]);
- } else {
- rescale = rsqrtf(
- sqrf(mv[I(2,0)]) + sqrf(mv[I(2,1)]) + sqrf(mv[I(2,2)]));
- }
- }
- GLfixed* const x = mvui.matrix.m;
- for (int i=0 ; i<3 ; i++) {
- x[I(i,0)] = gglFloatToFixed(mv[I(0,i)] * rescale);
- x[I(i,1)] = gglFloatToFixed(mv[I(1,i)] * rescale);
- x[I(i,2)] = gglFloatToFixed(mv[I(2,i)] * rescale);
- }
- mvui.picker();
- return;
- }
-
- GLfloat r[3][3];
- r[0][0] = det22(mv[I(1,1)], mv[I(2,1)], mv[I(1,2)], mv[I(2,2)]);
- r[0][1] =ndet22(mv[I(0,1)], mv[I(2,1)], mv[I(0,2)], mv[I(2,2)]);
- r[0][2] = det22(mv[I(0,1)], mv[I(1,1)], mv[I(0,2)], mv[I(1,2)]);
- r[1][0] =ndet22(mv[I(1,0)], mv[I(2,0)], mv[I(1,2)], mv[I(2,2)]);
- r[1][1] = det22(mv[I(0,0)], mv[I(2,0)], mv[I(0,2)], mv[I(2,2)]);
- r[1][2] =ndet22(mv[I(0,0)], mv[I(1,0)], mv[I(0,2)], mv[I(1,2)]);
- r[2][0] = det22(mv[I(1,0)], mv[I(2,0)], mv[I(1,1)], mv[I(2,1)]);
- r[2][1] =ndet22(mv[I(0,0)], mv[I(2,0)], mv[I(0,1)], mv[I(2,1)]);
- r[2][2] = det22(mv[I(0,0)], mv[I(1,0)], mv[I(0,1)], mv[I(1,1)]);
-
- GLfloat rdet;
- if (rescaleNormals == GL_RESCALE_NORMAL) {
- rdet = rsqrtf(sqrf(r[0][2]) + sqrf(r[1][2]) + sqrf(r[2][2]));
- } else {
- rdet = reciprocalf(
- r[0][0]*mv[I(0,0)] + r[0][1]*mv[I(1,0)] + r[0][2]*mv[I(2,0)]);
- }
+ // TODO: we need a faster invert, especially for when the modelview
+ // is a rigid-body matrix
+ invert(r, mv);
GLfixed* const x = mvui.matrix.m;
- for (int i=0 ; i<3 ; i++) {
- x[I(i,0)] = gglFloatToFixed(r[i][0] * rdet);
- x[I(i,1)] = gglFloatToFixed(r[i][1] * rdet);
- x[I(i,2)] = gglFloatToFixed(r[i][2] * rdet);
+ for (int i=0 ; i<4 ; i++) {
+ x[I(i,0)] = gglFloatToFixed(r[I(i,0)]);
+ x[I(i,1)] = gglFloatToFixed(r[I(i,1)]);
+ x[I(i,2)] = gglFloatToFixed(r[I(i,2)]);
+ x[I(i,4)] = gglFloatToFixed(r[I(i,3)]);
}
mvui.picker();
}
@@ -783,14 +737,19 @@
lhs->w = mla4(rx, m[ 3], ry, m[ 7], rz, m[11], rw, m[15]);
}
-void normal__generic(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) {
+void point4__mvui(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) {
+ // this used for transforming light positions back to object space.
+ // Lights have 3 components positions, so w is always 1.
+ // however, it is used as a switch for directional lights, so we need
+ // to preserve it.
const GLfixed* const m = mx->matrix.m;
const GLfixed rx = rhs->x;
const GLfixed ry = rhs->y;
const GLfixed rz = rhs->z;
- lhs->x = mla3(rx, m[ 0], ry, m[ 4], rz, m[ 8]);
- lhs->y = mla3(rx, m[ 1], ry, m[ 5], rz, m[ 9]);
- lhs->z = mla3(rx, m[ 2], ry, m[ 6], rz, m[10]);
+ lhs->x = mla3a(rx, m[ 0], ry, m[ 4], rz, m[ 8], m[12]);
+ lhs->y = mla3a(rx, m[ 1], ry, m[ 5], rz, m[ 9], m[13]);
+ lhs->z = mla3a(rx, m[ 2], ry, m[ 6], rz, m[10], m[14]);
+ lhs->w = rhs->w;
}
diff --git a/opengl/tests/lighting1709/Android.mk b/opengl/tests/lighting1709/Android.mk
new file mode 100644
index 0000000..9563e61
--- /dev/null
+++ b/opengl/tests/lighting1709/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := LightingTest
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/opengl/tests/lighting1709/AndroidManifest.xml b/opengl/tests/lighting1709/AndroidManifest.xml
new file mode 100644
index 0000000..6c23d42
--- /dev/null
+++ b/opengl/tests/lighting1709/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.lightingtest">
+
+ <application>
+ <activity android:name="ClearActivity" android:label="LightingTest">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java
new file mode 100644
index 0000000..3ae8c5c
--- /dev/null
+++ b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.lightingtest;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
+
+public class ClearActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mGLView = new ClearGLSurfaceView(this);
+ setContentView(mGLView);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mGLView.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mGLView.onResume();
+ }
+ private GLSurfaceView mGLView;
+}
+
+class ClearGLSurfaceView extends GLSurfaceView {
+ public ClearGLSurfaceView(Context context) {
+ super(context);
+ mRenderer = new ClearRenderer();
+ setRenderer(mRenderer);
+ }
+
+ ClearRenderer mRenderer;
+}
+
+class ClearRenderer implements GLSurfaceView.Renderer {
+ public ClearRenderer() {
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ // Do nothing special.
+ }
+
+ public void onSurfaceChanged(GL10 gl, int w, int h) {
+ // Compute the projection matrix
+ gl.glMatrixMode(GL10.GL_PROJECTION);
+ gl.glLoadIdentity();
+
+ // Compute the boundaries of the frustum
+ float fl = (float) (-(w / 2)) / 288;
+ float fr = (float) (w / 2) / 288;
+ float ft = (float) (h / 2) / 288;
+ float fb = (float) (-(h / 2)) / 288;
+
+ // Set the view frustum
+ gl.glFrustumf(fl, fr, fb, ft, 1.0f, 2000.0f);
+
+ // Set the viewport dimensions
+ gl.glMatrixMode(GL10.GL_MODELVIEW);
+ gl.glLoadIdentity();
+ gl.glViewport(0, 0, w, h);
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+ final float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f};
+ final float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f};
+ final float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f};
+ final float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f};
+
+ final float pos[] = {
+ -5.0f, -1.5f, 0.0f,
+ 0.0f, -1.5f, 0.0f,
+ 5.0f, -1.5f, 0.0f,
+ };
+
+ final float v[] = new float[9];
+ ByteBuffer vbb = ByteBuffer.allocateDirect(v.length*4);
+ vbb.order(ByteOrder.nativeOrder());
+ FloatBuffer vb = vbb.asFloatBuffer();
+
+ gl.glDisable(GL10.GL_DITHER);
+
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0);
+ gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosSpot, 0);
+ gl.glEnable(GL10.GL_LIGHT0);
+
+ gl.glEnable(GL10.GL_LIGHTING);
+
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glNormal3f(0, 0, 1);
+
+
+ // draw first 3 triangles, without using transforms
+ for (int i=0 ; i<3 ; i++) {
+ v[0] = -1; v[1] =-1; v[2] = -10;
+ v[3] = 0; v[4] = 1; v[5] = -10;
+ v[6] = 1; v[7] =-1; v[8] = -10;
+ for (int j=0 ; j<3 ; j++) {
+ v[j*3+0] -= pos[i*3+0];
+ v[j*3+1] -= pos[i*3+1];
+ v[j*3+2] -= pos[i*3+2];
+ }
+ vb.put(v).position(0);
+ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
+ gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
+ }
+
+ // draw the 2nd batch this time with transforms
+ v[0] = -1; v[1] =-1; v[2] = -10;
+ v[3] = 0; v[4] = 1; v[5] = -10;
+ v[6] = 1; v[7] =-1; v[8] = -10;
+ vb.put(v).position(0);
+ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb);
+
+ // draw lower left triangle
+ gl.glPushMatrix();
+ gl.glTranslatef(pos[0], pos[1], pos[2]);
+ gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
+ gl.glPopMatrix();
+
+ // draw lower middle triangle
+ gl.glPushMatrix();
+ gl.glTranslatef(pos[3], pos[4], pos[5]);
+ gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
+ gl.glPopMatrix();
+
+ // draw lower right triangle
+ gl.glPushMatrix();
+ gl.glTranslatef(pos[6], pos[7], pos[8]);
+ gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
+ gl.glPopMatrix();
+ }
+
+ public int[] getConfigSpec() {
+ int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
+ return configSpec;
+ }
+}
+
diff --git a/tts/java/android/tts/ITts.aidl b/tts/java/android/tts/ITts.aidl
index 1fe4a6a..3558f5a 100755
--- a/tts/java/android/tts/ITts.aidl
+++ b/tts/java/android/tts/ITts.aidl
@@ -1,59 +1,57 @@
-/*
- * Copyright (C) 2009 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.tts;
-
-import android.tts.ITtsCallback;
-
-import android.content.Intent;
-
-/**
- * AIDL for the TTS Service
- * ITts.java is autogenerated from this.
- *
- * {@hide}
- */
-interface ITts {
- void setEngine(in String engineName, in String[] requestedLanguages, in int strictness);
-
- void setEngineWithIntent(in Intent engineIntent);
-
- void setSpeechRate(in int speechRate);
-
- void speak(in String text, in int queueMode, in String[] params);
-
- boolean isSpeaking();
-
- void stop();
-
- void addSpeech(in String text, in String packageName, in int resId);
-
- void addSpeechFile(in String text, in String filename);
-
- void setLanguage(in String language);
-
- boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
-
- void playEarcon(in String earcon, in int queueMode, in String[] params);
-
- void addEarcon(in String earcon, in String packageName, in int resId);
-
- void addEarconFile(in String earcon, in String filename);
-
- void registerCallback(ITtsCallback cb);
-
- void unregisterCallback(ITtsCallback cb);
-}
+/*
+ * Copyright (C) 2009 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.tts;
+
+import android.tts.ITtsCallback;
+
+import android.content.Intent;
+
+/**
+ * AIDL for the TTS Service
+ * ITts.java is autogenerated from this.
+ *
+ * {@hide}
+ */
+interface ITts {
+ void setSpeechRate(in int speechRate);
+
+ void speak(in String text, in int queueMode, in String[] params);
+
+ boolean isSpeaking();
+
+ void stop();
+
+ void addSpeech(in String text, in String packageName, in int resId);
+
+ void addSpeechFile(in String text, in String filename);
+
+ void setLanguage(in String language);
+
+ boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
+
+ void playEarcon(in String earcon, in int queueMode, in String[] params);
+
+ void addEarcon(in String earcon, in String packageName, in int resId);
+
+ void addEarconFile(in String earcon, in String filename);
+
+ void registerCallback(ITtsCallback cb);
+
+ void unregisterCallback(ITtsCallback cb);
+
+ void playSilence(in long duration, in int queueMode, in String[] params);
+}
diff --git a/tts/java/android/tts/SynthProxy.java b/tts/java/android/tts/SynthProxy.java
new file mode 100755
index 0000000..e065f40
--- /dev/null
+++ b/tts/java/android/tts/SynthProxy.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * 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.tts;
+
+import android.util.Log;
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ *
+ * The SpeechSynthesis class provides a high-level api to create and play
+ * synthesized speech. This class is used internally to talk to a native
+ * TTS library that implements the interface defined in
+ * frameworks/base/include/tts/TtsEngine.h
+ *
+ */
+@SuppressWarnings("unused")
+public class SynthProxy {
+
+ //
+ // External API
+ //
+
+ /**
+ * Constructor; pass the location of the native TTS .so to use.
+ */
+ public SynthProxy(String nativeSoLib) {
+ Log.e("TTS is loading", nativeSoLib);
+ native_setup(new WeakReference<SynthProxy>(this), nativeSoLib);
+ }
+
+ /**
+ * Stops and clears the AudioTrack.
+ */
+ public void stop() {
+ native_stop(mJniData);
+ }
+
+ /**
+ * Synthesize speech and speak it directly using AudioTrack.
+ */
+ public void speak(String text) {
+ native_speak(mJniData, text);
+ }
+
+ /**
+ * Synthesize speech to a file. The current implementation writes a valid
+ * WAV file to the given path, assuming it is writable. Something like
+ * "/sdcard/???.wav" is recommended.
+ */
+ public void synthesizeToFile(String text, String filename) {
+ native_synthesizeToFile(mJniData, text, filename);
+ }
+
+ // TODO add IPA methods
+
+ /**
+ * Sets the language
+ */
+ public void setLanguage(String language) {
+ native_setLanguage(mJniData, language);
+ }
+
+ /**
+ * Sets the speech rate
+ */
+ public final void setSpeechRate(int speechRate) {
+ native_setSpeechRate(mJniData, speechRate);
+ }
+
+
+ /**
+ * Plays the given audio buffer
+ */
+ public void playAudioBuffer(int bufferPointer, int bufferSize) {
+ native_playAudioBuffer(mJniData, bufferPointer, bufferSize);
+ }
+
+ /**
+ * Gets the currently set language
+ */
+ public String getLanguage() {
+ return native_getLanguage(mJniData);
+ }
+
+ /**
+ * Gets the currently set rate
+ */
+ public int getRate() {
+ return native_getRate(mJniData);
+ }
+
+ /**
+ * Shuts down the native synthesizer
+ */
+ public void shutdown() {
+ native_shutdown(mJniData);
+ }
+
+ //
+ // Internal
+ //
+
+ protected void finalize() {
+ native_finalize(mJniData);
+ mJniData = 0;
+ }
+
+ static {
+ System.loadLibrary("ttssynthproxy");
+ }
+
+ private final static String TAG = "SynthProxy";
+
+ /**
+ * Accessed by native methods
+ */
+ private int mJniData = 0;
+
+ private native final void native_setup(Object weak_this,
+ String nativeSoLib);
+
+ private native final void native_finalize(int jniData);
+
+ private native final void native_stop(int jniData);
+
+ private native final void native_speak(int jniData, String text);
+
+ private native final void native_synthesizeToFile(int jniData, String text, String filename);
+
+ private native final void native_setLanguage(int jniData, String language);
+
+ private native final void native_setSpeechRate(int jniData, int speechRate);
+
+ // TODO add buffer format
+ private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize);
+
+ private native final String native_getLanguage(int jniData);
+
+ private native final int native_getRate(int jniData);
+
+ private native final void native_shutdown(int jniData);
+
+
+ /**
+ * Callback from the C layer
+ */
+ @SuppressWarnings("unused")
+ private static void postNativeSpeechSynthesizedInJava(Object tts_ref,
+ int bufferPointer, int bufferSize) {
+
+ Log.i("TTS plugin debug", "bufferPointer: " + bufferPointer
+ + " bufferSize: " + bufferSize);
+
+ SynthProxy nativeTTS = (SynthProxy)((WeakReference)tts_ref).get();
+ // TODO notify TTS service of synthesis/playback completion,
+ // method definition to be changed.
+ }
+}
diff --git a/tts/java/android/tts/TtsService.java b/tts/java/android/tts/TtsService.java
new file mode 100755
index 0000000..2373e96
--- /dev/null
+++ b/tts/java/android/tts/TtsService.java
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * 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.tts;
+
+import android.tts.ITts.Stub;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @hide Synthesizes speech from text. This is implemented as a service so that
+ * other applications can call the TTS without needing to bundle the TTS
+ * in the build.
+ *
+ */
+public class TtsService extends Service implements OnCompletionListener {
+
+ private static class SpeechItem {
+ public static final int SPEECH = 0;
+ public static final int EARCON = 1;
+ public static final int SILENCE = 2;
+ public String mText = null;
+ public ArrayList<String> mParams = null;
+ public int mType = SPEECH;
+ public long mDuration = 0;
+
+ public SpeechItem(String text, ArrayList<String> params, int itemType) {
+ mText = text;
+ mParams = params;
+ mType = itemType;
+ }
+
+ public SpeechItem(long silenceTime) {
+ mDuration = silenceTime;
+ }
+ }
+
+ /**
+ * Contains the information needed to access a sound resource; the name of
+ * the package that contains the resource and the resID of the resource
+ * within that package.
+ */
+ private static class SoundResource {
+ public String mSourcePackageName = null;
+ public int mResId = -1;
+ public String mFilename = null;
+
+ public SoundResource(String packageName, int id) {
+ mSourcePackageName = packageName;
+ mResId = id;
+ mFilename = null;
+ }
+
+ public SoundResource(String file) {
+ mSourcePackageName = null;
+ mResId = -1;
+ mFilename = file;
+ }
+ }
+
+ private static final String ACTION = "android.intent.action.USE_TTS";
+ private static final String CATEGORY = "android.intent.category.TTS";
+ private static final String PKGNAME = "android.tts";
+
+ final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>();
+
+ private Boolean mIsSpeaking;
+ private ArrayList<SpeechItem> mSpeechQueue;
+ private HashMap<String, SoundResource> mEarcons;
+ private HashMap<String, SoundResource> mUtterances;
+ private MediaPlayer mPlayer;
+ private TtsService mSelf;
+
+ private SharedPreferences prefs;
+
+ private final ReentrantLock speechQueueLock = new ReentrantLock();
+ private final ReentrantLock synthesizerLock = new ReentrantLock();
+
+ private SynthProxy nativeSynth;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i("TTS", "TTS starting");
+
+ // TODO: Make this work when the settings are done in the main Settings
+ // app.
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ PackageManager pm = this.getPackageManager();
+ String soLibPath = "";
+ try {
+ soLibPath = pm.getApplicationInfo("com.svox.pico", 0).dataDir;
+ } catch (NameNotFoundException e) {
+ // This exception cannot actually happen as com.svox.pico is
+ // included with the system image.
+ e.printStackTrace();
+ }
+ soLibPath = soLibPath + "/lib/libttspico.so";
+ nativeSynth = new SynthProxy(soLibPath);
+
+ mSelf = this;
+ mIsSpeaking = false;
+
+ mEarcons = new HashMap<String, SoundResource>();
+ mUtterances = new HashMap<String, SoundResource>();
+
+ mSpeechQueue = new ArrayList<SpeechItem>();
+ mPlayer = null;
+
+ setLanguage(prefs.getString("lang_pref", "en-rUS"));
+ setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140")));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // Don't hog the media player
+ cleanUpPlayer();
+
+ nativeSynth.shutdown();
+
+ // Unregister all callbacks.
+ mCallbacks.kill();
+ }
+
+ private void setSpeechRate(int rate) {
+ if (prefs.getBoolean("override_pref", false)) {
+ // This is set to the default here so that the preview in the prefs
+ // activity will show the change without a restart, even if apps are
+ // not allowed to change the defaults.
+ rate = Integer.parseInt(prefs.getString("rate_pref", "140"));
+ }
+ nativeSynth.setSpeechRate(rate);
+ }
+
+ private void setLanguage(String lang) {
+ if (prefs.getBoolean("override_pref", false)) {
+ // This is set to the default here so that the preview in the prefs
+ // activity will show the change without a restart, even if apps are
+ // not
+ // allowed to change the defaults.
+ lang = prefs.getString("lang_pref", "en-rUS");
+ }
+ nativeSynth.setLanguage(lang);
+ }
+
+ /**
+ * Adds a sound resource to the TTS.
+ *
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ private void addSpeech(String text, String packageName, int resId) {
+ mUtterances.put(text, new SoundResource(packageName, resId));
+ }
+
+ /**
+ * Adds a sound resource to the TTS.
+ *
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a complete
+ * path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ private void addSpeech(String text, String filename) {
+ mUtterances.put(text, new SoundResource(filename));
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ private void addEarcon(String earcon, String packageName, int resId) {
+ mEarcons.put(earcon, new SoundResource(packageName, resId));
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a complete
+ * path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ private void addEarcon(String earcon, String filename) {
+ mEarcons.put(earcon, new SoundResource(filename));
+ }
+
+ /**
+ * Speaks the given text using the specified queueing mode and parameters.
+ *
+ * @param text
+ * The text that should be spoken
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters. This is not implemented for all
+ * engines.
+ */
+ private void speak(String text, int queueMode, ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
+ }
+
+ /**
+ * Plays the earcon using the specified queueing mode and parameters.
+ *
+ * @param earcon
+ * The earcon that should be played
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters. This is not implemented for all
+ * engines.
+ */
+ private void playEarcon(String earcon, int queueMode,
+ ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
+ }
+
+ /**
+ * Stops all speech output and removes any utterances still in the queue.
+ */
+ private void stop() {
+ Log.i("TTS", "Stopping");
+ mSpeechQueue.clear();
+
+ nativeSynth.stop();
+ mIsSpeaking = false;
+ if (mPlayer != null) {
+ try {
+ mPlayer.stop();
+ } catch (IllegalStateException e) {
+ // Do nothing, the player is already stopped.
+ }
+ }
+ Log.i("TTS", "Stopped");
+ }
+
+ public void onCompletion(MediaPlayer arg0) {
+ processSpeechQueue();
+ }
+
+ private void playSilence(long duration, int queueMode,
+ ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(duration));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
+ }
+
+ private void silence(final long duration) {
+ class SilenceThread implements Runnable {
+ public void run() {
+ try {
+ Thread.sleep(duration);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ processSpeechQueue();
+ }
+ }
+ }
+ Thread slnc = (new Thread(new SilenceThread()));
+ slnc.setPriority(Thread.MIN_PRIORITY);
+ slnc.start();
+ }
+
+ private void speakInternalOnly(final String text,
+ final ArrayList<String> params) {
+ class SynthThread implements Runnable {
+ public void run() {
+ boolean synthAvailable = false;
+ try {
+ synthAvailable = synthesizerLock.tryLock();
+ if (!synthAvailable) {
+ Thread.sleep(100);
+ Thread synth = (new Thread(new SynthThread()));
+ synth.setPriority(Thread.MIN_PRIORITY);
+ synth.start();
+ return;
+ }
+ nativeSynth.speak(text);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ // This check is needed because finally will always run;
+ // even if the
+ // method returns somewhere in the try block.
+ if (synthAvailable) {
+ synthesizerLock.unlock();
+ }
+ }
+ }
+ }
+ Thread synth = (new Thread(new SynthThread()));
+ synth.setPriority(Thread.MIN_PRIORITY);
+ synth.start();
+ }
+
+ private SoundResource getSoundResource(SpeechItem speechItem) {
+ SoundResource sr = null;
+ String text = speechItem.mText;
+ if (speechItem.mType == SpeechItem.SILENCE) {
+ // Do nothing if this is just silence
+ } else if (speechItem.mType == SpeechItem.EARCON) {
+ sr = mEarcons.get(text);
+ } else {
+ sr = mUtterances.get(text);
+ }
+ return sr;
+ }
+
+ private void dispatchSpeechCompletedCallbacks(String mark) {
+ Log.i("TTS callback", "dispatch started");
+ // Broadcast to all clients the new value.
+ final int N = mCallbacks.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).markReached(mark);
+ } catch (RemoteException e) {
+ // The RemoteCallbackList will take care of removing
+ // the dead object for us.
+ }
+ }
+ mCallbacks.finishBroadcast();
+ Log.i("TTS callback", "dispatch completed to " + N);
+ }
+
+ private void processSpeechQueue() {
+ boolean speechQueueAvailable = false;
+ try {
+ speechQueueAvailable = speechQueueLock.tryLock();
+ if (!speechQueueAvailable) {
+ return;
+ }
+ if (mSpeechQueue.size() < 1) {
+ mIsSpeaking = false;
+ // Dispatch a completion here as this is the
+ // only place where speech completes normally.
+ // Nothing left to say in the queue is a special case
+ // that is always a "mark" - associated text is null.
+ dispatchSpeechCompletedCallbacks("");
+ return;
+ }
+
+ SpeechItem currentSpeechItem = mSpeechQueue.get(0);
+ mIsSpeaking = true;
+ SoundResource sr = getSoundResource(currentSpeechItem);
+ // Synth speech as needed - synthesizer should call
+ // processSpeechQueue to continue running the queue
+ Log.i("TTS processing: ", currentSpeechItem.mText);
+ if (sr == null) {
+ if (currentSpeechItem.mType == SpeechItem.SPEECH) {
+ // TODO: Split text up into smaller chunks before accepting
+ // them for processing.
+ speakInternalOnly(currentSpeechItem.mText,
+ currentSpeechItem.mParams);
+ } else {
+ // This is either silence or an earcon that was missing
+ silence(currentSpeechItem.mDuration);
+ }
+ } else {
+ cleanUpPlayer();
+ if (sr.mSourcePackageName == PKGNAME) {
+ // Utterance is part of the TTS library
+ mPlayer = MediaPlayer.create(this, sr.mResId);
+ } else if (sr.mSourcePackageName != null) {
+ // Utterance is part of the app calling the library
+ Context ctx;
+ try {
+ ctx = this.createPackageContext(sr.mSourcePackageName,
+ 0);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ mSpeechQueue.remove(0); // Remove it from the queue and
+ // move on
+ mIsSpeaking = false;
+ return;
+ }
+ mPlayer = MediaPlayer.create(ctx, sr.mResId);
+ } else {
+ // Utterance is coming from a file
+ mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename));
+ }
+
+ // Check if Media Server is dead; if it is, clear the queue and
+ // give up for now - hopefully, it will recover itself.
+ if (mPlayer == null) {
+ mSpeechQueue.clear();
+ mIsSpeaking = false;
+ return;
+ }
+ mPlayer.setOnCompletionListener(this);
+ try {
+ mPlayer.start();
+ } catch (IllegalStateException e) {
+ mSpeechQueue.clear();
+ mIsSpeaking = false;
+ cleanUpPlayer();
+ return;
+ }
+ }
+ if (mSpeechQueue.size() > 0) {
+ mSpeechQueue.remove(0);
+ }
+ } finally {
+ // This check is needed because finally will always run; even if the
+ // method returns somewhere in the try block.
+ if (speechQueueAvailable) {
+ speechQueueLock.unlock();
+ }
+ }
+ }
+
+ private void cleanUpPlayer() {
+ if (mPlayer != null) {
+ mPlayer.release();
+ mPlayer = null;
+ }
+ }
+
+ /**
+ * Synthesizes the given text using the specified queuing mode and
+ * parameters.
+ *
+ * @param text
+ * The String of text that should be synthesized
+ * @param params
+ * An ArrayList of parameters. The first element of this array
+ * controls the type of voice to use.
+ * @param filename
+ * The string that gives the full output filename; it should be
+ * something like "/sdcard/myappsounds/mysound.wav".
+ * @return A boolean that indicates if the synthesis succeeded
+ */
+ private boolean synthesizeToFile(String text, ArrayList<String> params,
+ String filename, boolean calledFromApi) {
+ // Only stop everything if this is a call made by an outside app trying
+ // to
+ // use the API. Do NOT stop if this is a call from within the service as
+ // clearing the speech queue here would be a mistake.
+ if (calledFromApi) {
+ stop();
+ }
+ Log.i("TTS", "Synthesizing to " + filename);
+ boolean synthAvailable = false;
+ try {
+ synthAvailable = synthesizerLock.tryLock();
+ if (!synthAvailable) {
+ return false;
+ }
+ // Don't allow a filename that is too long
+ // TODO use platform constant
+ if (filename.length() > 250) {
+ return false;
+ }
+ nativeSynth.synthesizeToFile(text, filename);
+ } finally {
+ // This check is needed because finally will always run; even if the
+ // method returns somewhere in the try block.
+ if (synthAvailable) {
+ synthesizerLock.unlock();
+ }
+ }
+ Log.i("TTS", "Completed synthesis for " + filename);
+ return true;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (ACTION.equals(intent.getAction())) {
+ for (String category : intent.getCategories()) {
+ if (category.equals(CATEGORY)) {
+ return mBinder;
+ }
+ }
+ }
+ return null;
+ }
+
+ private final ITts.Stub mBinder = new Stub() {
+
+ public void registerCallback(ITtsCallback cb) {
+ if (cb != null)
+ mCallbacks.register(cb);
+ }
+
+ public void unregisterCallback(ITtsCallback cb) {
+ if (cb != null)
+ mCallbacks.unregister(cb);
+ }
+
+ /**
+ * Speaks the given text using the specified queueing mode and
+ * parameters.
+ *
+ * @param text
+ * The text that should be spoken
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters. The first element of this
+ * array controls the type of voice to use.
+ */
+ public void speak(String text, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.speak(text, queueMode, speakingParams);
+ }
+
+ /**
+ * Plays the earcon using the specified queueing mode and parameters.
+ *
+ * @param earcon
+ * The earcon that should be played
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters.
+ */
+ public void playEarcon(String earcon, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.playEarcon(earcon, queueMode, speakingParams);
+ }
+
+ /**
+ * Plays the silence using the specified queueing mode and parameters.
+ *
+ * @param duration
+ * The duration of the silence that should be played
+ * @param queueMode
+ * 0 for no queue (interrupts all previous utterances), 1 for
+ * queued
+ * @param params
+ * An ArrayList of parameters.
+ */
+ public void playSilence(long duration, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.playSilence(duration, queueMode, speakingParams);
+ }
+
+ /**
+ * Stops all speech output and removes any utterances still in the
+ * queue.
+ */
+ public void stop() {
+ mSelf.stop();
+ }
+
+ /**
+ * Returns whether or not the TTS is speaking.
+ *
+ * @return Boolean to indicate whether or not the TTS is speaking
+ */
+ public boolean isSpeaking() {
+ return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1));
+ }
+
+ /**
+ * Adds a sound resource to the TTS.
+ *
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ public void addSpeech(String text, String packageName, int resId) {
+ mSelf.addSpeech(text, packageName, resId);
+ }
+
+ /**
+ * Adds a sound resource to the TTS.
+ *
+ * @param text
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a
+ * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ public void addSpeechFile(String text, String filename) {
+ mSelf.addSpeech(text, filename);
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param packageName
+ * The name of the package which has the sound resource
+ * @param resId
+ * The resource ID of the sound within its package
+ */
+ public void addEarcon(String earcon, String packageName, int resId) {
+ mSelf.addEarcon(earcon, packageName, resId);
+ }
+
+ /**
+ * Adds a sound resource to the TTS as an earcon.
+ *
+ * @param earcon
+ * The text that should be associated with the sound resource
+ * @param filename
+ * The filename of the sound resource. This must be a
+ * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
+ */
+ public void addEarconFile(String earcon, String filename) {
+ mSelf.addEarcon(earcon, filename);
+ }
+
+ /**
+ * Sets the speech rate for the TTS. Note that this will only have an
+ * effect on synthesized speech; it will not affect pre-recorded speech.
+ *
+ * @param speechRate
+ * The speech rate that should be used
+ */
+ public void setSpeechRate(int speechRate) {
+ mSelf.setSpeechRate(speechRate);
+ }
+
+ /**
+ * Sets the speech rate for the TTS. Note that this will only have an
+ * effect on synthesized speech; it will not affect pre-recorded speech.
+ *
+ * @param language
+ * Language values are based on the Android conventions for
+ * localization as described in the Android platform
+ * documentation on internationalization. This implies that
+ * language data is specified in the format xx-rYY, where xx
+ * is a two letter ISO 639-1 language code in lowercase and
+ * rYY is a two letter ISO 3166-1-alpha-2 language code in
+ * uppercase preceded by a lowercase "r".
+ */
+ public void setLanguage(String language) {
+ mSelf.setLanguage(language);
+ }
+
+ /**
+ * Speaks the given text using the specified queueing mode and
+ * parameters.
+ *
+ * @param text
+ * The String of text that should be synthesized
+ * @param params
+ * An ArrayList of parameters. The first element of this
+ * array controls the type of voice to use.
+ * @param filename
+ * The string that gives the full output filename; it should
+ * be something like "/sdcard/myappsounds/mysound.wav".
+ * @return A boolean that indicates if the synthesis succeeded
+ */
+ public boolean synthesizeToFile(String text, String[] params,
+ String filename) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ return mSelf.synthesizeToFile(text, speakingParams, filename, true);
+ }
+ };
+
+}
diff --git a/tts/jni/Android.mk b/tts/jni/Android.mk
new file mode 100755
index 0000000..665d6d2
--- /dev/null
+++ b/tts/jni/Android.mk
@@ -0,0 +1,31 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ android_tts_SynthProxy.cpp
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE)
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroid_runtime \
+ libnativehelper \
+ libmedia \
+ libutils \
+ libcutils
+
+ifeq ($(TARGET_SIMULATOR),true)
+ LOCAL_LDLIBS += -ldl
+else
+ LOCAL_SHARED_LIBRARIES += libdl
+endif
+
+
+LOCAL_MODULE:= libttssynthproxy
+
+LOCAL_ARM_MODE := arm
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/tts/jni/android_tts_SynthProxy.cpp b/tts/jni/android_tts_SynthProxy.cpp
new file mode 100644
index 0000000..582e621
--- /dev/null
+++ b/tts/jni/android_tts_SynthProxy.cpp
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+
+#define LOG_TAG "SynthProxy"
+
+#include <utils/Log.h>
+#include <nativehelper/jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <tts/TtsEngine.h>
+#include <media/AudioTrack.h>
+
+#include <dlfcn.h>
+
+#define DEFAULT_TTS_RATE 16000
+#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT
+#define DEFAULT_TTS_NB_CHANNELS 1
+#define DEFAULT_TTS_BUFFERSIZE 1024
+
+#define USAGEMODE_PLAY_IMMEDIATELY 0
+#define USAGEMODE_WRITE_TO_FILE 1
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+struct fields_t {
+ jfieldID synthProxyFieldJniData;
+ jclass synthProxyClass;
+ jmethodID synthProxyMethodPost;
+};
+
+struct afterSynthData_t {
+ jint jniStorage;
+ int usageMode;
+ FILE* outputFile;
+};
+
+// ----------------------------------------------------------------------------
+static fields_t javaTTSFields;
+
+// ----------------------------------------------------------------------------
+class SynthProxyJniStorage {
+ public :
+ //jclass tts_class;
+ jobject tts_ref;
+ TtsEngine* mNativeSynthInterface;
+ AudioTrack* mAudioOut;
+ uint32_t mSampleRate;
+ AudioSystem::audio_format mAudFormat;
+ int mNbChannels;
+ int8_t * mBuffer;
+ size_t mBufferSize;
+
+ SynthProxyJniStorage() {
+ //tts_class = NULL;
+ tts_ref = NULL;
+ mNativeSynthInterface = NULL;
+ mAudioOut = NULL;
+ mSampleRate = DEFAULT_TTS_RATE;
+ mAudFormat = DEFAULT_TTS_FORMAT;
+ mNbChannels = DEFAULT_TTS_NB_CHANNELS;
+ mBufferSize = DEFAULT_TTS_BUFFERSIZE;
+ mBuffer = new int8_t[mBufferSize];
+ }
+
+ ~SynthProxyJniStorage() {
+ killAudio();
+ if (mNativeSynthInterface) {
+ mNativeSynthInterface->shutdown();
+ mNativeSynthInterface = NULL;
+ }
+ delete mBuffer;
+ }
+
+ void killAudio() {
+ if (mAudioOut) {
+ mAudioOut->stop();
+ delete mAudioOut;
+ mAudioOut = NULL;
+ }
+ }
+
+ void createAudioOut(uint32_t rate, AudioSystem::audio_format format,
+ int channel) {
+ mSampleRate = rate;
+ mAudFormat = format;
+ mNbChannels = channel;
+
+ // TODO use the TTS stream type
+ int streamType = AudioSystem::MUSIC;
+
+ // retrieve system properties to ensure successful creation of the
+ // AudioTrack object for playback
+ int afSampleRate;
+ if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
+ afSampleRate = 44100;
+ }
+ int afFrameCount;
+ if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
+ afFrameCount = 2048;
+ }
+ uint32_t afLatency;
+ if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
+ afLatency = 500;
+ }
+ uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate);
+ if (minBufCount < 2) minBufCount = 2;
+ int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate;
+
+ mAudioOut = new AudioTrack(streamType, rate, format, channel,
+ minFrameCount > 4096 ? minFrameCount : 4096,
+ 0, 0, 0, 0); // not using an AudioTrack callback
+
+ if (mAudioOut->initCheck() != NO_ERROR) {
+ LOGI("AudioTrack error");
+ delete mAudioOut;
+ mAudioOut = NULL;
+ } else {
+ LOGI("AudioTrack OK");
+ mAudioOut->start();
+ LOGI("AudioTrack started");
+ }
+ }
+};
+
+
+// ----------------------------------------------------------------------------
+void prepAudioTrack(SynthProxyJniStorage* pJniData,
+ uint32_t rate, AudioSystem::audio_format format, int channel)
+{
+ // Don't bother creating a new audiotrack object if the current
+ // object is already set.
+ if ( pJniData->mAudioOut &&
+ (rate == pJniData->mSampleRate) &&
+ (format == pJniData->mAudFormat) &&
+ (channel == pJniData->mNbChannels) ){
+ return;
+ }
+ if (pJniData->mAudioOut){
+ pJniData->killAudio();
+ }
+ pJniData->createAudioOut(rate, format, channel);
+}
+
+
+// ----------------------------------------------------------------------------
+/*
+ * Callback from TTS engine.
+ * Directly speaks using AudioTrack or write to file
+ */
+static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,
+ AudioSystem::audio_format format, int channel,
+ int8_t *&wav, size_t &bufferSize, tts_synth_status status) {
+ LOGI("ttsSynthDoneCallback: %d bytes", bufferSize);
+
+ if (userdata == NULL){
+ LOGE("userdata == NULL");
+ return TTS_CALLBACK_HALT;
+ }
+ afterSynthData_t* pForAfter = (afterSynthData_t*)userdata;
+ SynthProxyJniStorage* pJniData = (SynthProxyJniStorage*)(pForAfter->jniStorage);
+
+ if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){
+ LOGI("Direct speech");
+
+ if (wav == NULL) {
+ delete pForAfter;
+ LOGI("Null: speech has completed");
+ }
+
+ if (bufferSize > 0) {
+ prepAudioTrack(pJniData, rate, format, channel);
+ if (pJniData->mAudioOut) {
+ pJniData->mAudioOut->write(wav, bufferSize);
+ LOGI("AudioTrack wrote: %d bytes", bufferSize);
+ } else {
+ LOGI("Can't play, null audiotrack");
+ }
+ }
+ } else if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) {
+ LOGI("Save to file");
+ if (wav == NULL) {
+ delete pForAfter;
+ LOGI("Null: speech has completed");
+ }
+ if (bufferSize > 0){
+ fwrite(wav, 1, bufferSize, pForAfter->outputFile);
+ }
+ }
+ // TODO update to call back into the SynthProxy class through the
+ // javaTTSFields.synthProxyMethodPost methode to notify
+ // playback has completed if the synthesis is done, i.e.
+ // if status == TTS_SYNTH_DONE
+ //delete pForAfter;
+
+ // we don't update the wav (output) parameter as we'll let the next callback
+ // write at the same location, we've consumed the data already, but we need
+ // to update bufferSize to let the TTS engine know how much it can write the
+ // next time it calls this function.
+ bufferSize = pJniData->mBufferSize;
+
+ return TTS_CALLBACK_CONTINUE;
+}
+
+
+// ----------------------------------------------------------------------------
+static void
+android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
+ jobject weak_this, jstring nativeSoLib)
+{
+ SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage();
+
+ prepAudioTrack(pJniStorage,
+ DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);
+
+ const char *nativeSoLibNativeString =
+ env->GetStringUTFChars(nativeSoLib, 0);
+
+ void *engine_lib_handle = dlopen(nativeSoLibNativeString,
+ RTLD_NOW | RTLD_LOCAL);
+ if (engine_lib_handle==NULL) {
+ LOGI("engine_lib_handle==NULL");
+ // TODO report error so the TTS can't be used
+ } else {
+ TtsEngine *(*get_TtsEngine)() =
+ reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
+
+ pJniStorage->mNativeSynthInterface = (*get_TtsEngine)();
+
+ if (pJniStorage->mNativeSynthInterface) {
+ pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB);
+ }
+ }
+
+ // we use a weak reference so the SynthProxy object can be garbage collected.
+ pJniStorage->tts_ref = env->NewGlobalRef(weak_this);
+
+ // save the JNI resources so we can use them (and free them) later
+ env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData,
+ (int)pJniStorage);
+
+ env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString);
+}
+
+
+static void
+android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData)
+{
+ if (jniData) {
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ delete pSynthData;
+ }
+}
+
+
+static void
+android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
+ jstring language)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ const char *langNativeString = env->GetStringUTFChars(language, 0);
+ // TODO check return codes
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->setLanguage(langNativeString,
+ strlen(langNativeString));
+ }
+ env->ReleaseStringUTFChars(language, langNativeString);
+}
+
+
+static void
+android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData,
+ int speechRate)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data");
+ return;
+ }
+
+ int bufSize = 10;
+ char buffer [bufSize];
+ sprintf(buffer, "%d", speechRate);
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ LOGI("setting speech rate to %d", speechRate);
+ // TODO check return codes
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->setProperty("rate", buffer, bufSize);
+ }
+}
+
+
+// TODO: Refactor this to get rid of any assumptions about sample rate, etc.
+static void
+android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData,
+ jstring textJavaString, jstring filenameJavaString)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+
+ const char *filenameNativeString =
+ env->GetStringUTFChars(filenameJavaString, 0);
+ const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
+
+ afterSynthData_t* pForAfter = new (afterSynthData_t);
+ pForAfter->jniStorage = jniData;
+ pForAfter->usageMode = USAGEMODE_WRITE_TO_FILE;
+
+ pForAfter->outputFile = fopen(filenameNativeString, "wb");
+
+ // Write 44 blank bytes for WAV header, then come back and fill them in
+ // after we've written the audio data
+ char header[44];
+ fwrite(header, 1, 44, pForAfter->outputFile);
+
+ unsigned int unique_identifier;
+
+ // TODO check return codes
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize,
+ (void *)pForAfter);
+ }
+
+ long filelen = ftell(pForAfter->outputFile);
+
+ int samples = (((int)filelen) - 44) / 2;
+ header[0] = 'R';
+ header[1] = 'I';
+ header[2] = 'F';
+ header[3] = 'F';
+ ((uint32_t *)(&header[4]))[0] = filelen - 8;
+ header[8] = 'W';
+ header[9] = 'A';
+ header[10] = 'V';
+ header[11] = 'E';
+
+ header[12] = 'f';
+ header[13] = 'm';
+ header[14] = 't';
+ header[15] = ' ';
+
+ ((uint32_t *)(&header[16]))[0] = 16; // size of fmt
+
+ ((unsigned short *)(&header[20]))[0] = 1; // format
+ ((unsigned short *)(&header[22]))[0] = 1; // channels
+ ((uint32_t *)(&header[24]))[0] = 22050; // samplerate
+ ((uint32_t *)(&header[28]))[0] = 44100; // byterate
+ ((unsigned short *)(&header[32]))[0] = 2; // block align
+ ((unsigned short *)(&header[34]))[0] = 16; // bits per sample
+
+ header[36] = 'd';
+ header[37] = 'a';
+ header[38] = 't';
+ header[39] = 'a';
+
+ ((uint32_t *)(&header[40]))[0] = samples * 2; // size of data
+
+ // Skip back to the beginning and rewrite the header
+ fseek(pForAfter->outputFile, 0, SEEK_SET);
+ fwrite(header, 1, 44, pForAfter->outputFile);
+
+ fflush(pForAfter->outputFile);
+ fclose(pForAfter->outputFile);
+
+ env->ReleaseStringUTFChars(textJavaString, textNativeString);
+ env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString);
+}
+
+
+static void
+android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
+ jstring textJavaString)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_speak(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+
+ if (pSynthData->mAudioOut) {
+ pSynthData->mAudioOut->stop();
+ pSynthData->mAudioOut->start();
+ }
+
+ afterSynthData_t* pForAfter = new (afterSynthData_t);
+ pForAfter->jniStorage = jniData;
+ pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY;
+
+ if (pSynthData->mNativeSynthInterface) {
+ const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
+ pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize,
+ (void *)pForAfter);
+ env->ReleaseStringUTFChars(textJavaString, textNativeString);
+ }
+}
+
+
+static void
+android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->stop();
+ }
+ if (pSynthData->mAudioOut) {
+ pSynthData->mAudioOut->stop();
+ }
+}
+
+
+static void
+android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_shutdown(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->shutdown();
+ pSynthData->mNativeSynthInterface = NULL;
+ }
+}
+
+
+// TODO add buffer format
+static void
+android_tts_SynthProxy_playAudioBuffer(JNIEnv *env, jobject thiz, jint jniData,
+ int bufferPointer, int bufferSize)
+{
+LOGI("android_tts_SynthProxy_playAudioBuffer");
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_playAudioBuffer(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ short* wav = (short*) bufferPointer;
+ pSynthData->mAudioOut->write(wav, bufferSize);
+ LOGI("AudioTrack wrote: %d bytes", bufferSize);
+}
+
+
+JNIEXPORT jstring JNICALL
+android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data");
+ return env->NewStringUTF("");
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ size_t bufSize = 100;
+ char buf[bufSize];
+ memset(buf, 0, bufSize);
+ // TODO check return codes
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->getLanguage(buf, &bufSize);
+ }
+ return env->NewStringUTF(buf);
+}
+
+JNIEXPORT int JNICALL
+android_tts_SynthProxy_getRate(JNIEnv *env, jobject thiz, jint jniData)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_getRate(): invalid JNI data");
+ return 0;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ size_t bufSize = 100;
+
+ char buf[bufSize];
+ memset(buf, 0, bufSize);
+ // TODO check return codes
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->getProperty("rate", buf, &bufSize);
+ }
+ return atoi(buf);
+}
+
+// Dalvik VM type signatures
+static JNINativeMethod gMethods[] = {
+ { "native_stop",
+ "(I)V",
+ (void*)android_tts_SynthProxy_stop
+ },
+ { "native_speak",
+ "(ILjava/lang/String;)V",
+ (void*)android_tts_SynthProxy_speak
+ },
+ { "native_synthesizeToFile",
+ "(ILjava/lang/String;Ljava/lang/String;)V",
+ (void*)android_tts_SynthProxy_synthesizeToFile
+ },
+ { "native_setLanguage",
+ "(ILjava/lang/String;)V",
+ (void*)android_tts_SynthProxy_setLanguage
+ },
+ { "native_setSpeechRate",
+ "(II)V",
+ (void*)android_tts_SynthProxy_setSpeechRate
+ },
+ { "native_playAudioBuffer",
+ "(III)V",
+ (void*)android_tts_SynthProxy_playAudioBuffer
+ },
+ { "native_getLanguage",
+ "(I)Ljava/lang/String;",
+ (void*)android_tts_SynthProxy_getLanguage
+ },
+ { "native_getRate",
+ "(I)I",
+ (void*)android_tts_SynthProxy_getRate
+ },
+ { "native_shutdown",
+ "(I)V",
+ (void*)android_tts_SynthProxy_shutdown
+ },
+ { "native_setup",
+ "(Ljava/lang/Object;Ljava/lang/String;)V",
+ (void*)android_tts_SynthProxy_native_setup
+ },
+ { "native_finalize",
+ "(I)V",
+ (void*)android_tts_SynthProxy_native_finalize
+ }
+};
+
+#define SP_JNIDATA_FIELD_NAME "mJniData"
+#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava"
+
+// TODO: verify this is the correct path
+static const char* const kClassPathName = "android/tts/SynthProxy";
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+ jclass clazz;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("ERROR: GetEnv failed\n");
+ goto bail;
+ }
+ assert(env != NULL);
+
+ clazz = env->FindClass(kClassPathName);
+ if (clazz == NULL) {
+ LOGE("Can't find %s", kClassPathName);
+ goto bail;
+ }
+
+ javaTTSFields.synthProxyClass = clazz;
+ javaTTSFields.synthProxyFieldJniData = NULL;
+ javaTTSFields.synthProxyMethodPost = NULL;
+
+ javaTTSFields.synthProxyFieldJniData = env->GetFieldID(clazz,
+ SP_JNIDATA_FIELD_NAME, "I");
+ if (javaTTSFields.synthProxyFieldJniData == NULL) {
+ LOGE("Can't find %s.%s field", kClassPathName, SP_JNIDATA_FIELD_NAME);
+ goto bail;
+ }
+
+ javaTTSFields.synthProxyMethodPost = env->GetStaticMethodID(clazz,
+ SP_POSTSPEECHSYNTHESIZED_METHOD_NAME, "(Ljava/lang/Object;II)V");
+ if (javaTTSFields.synthProxyMethodPost == NULL) {
+ LOGE("Can't find %s.%s method", kClassPathName, SP_POSTSPEECHSYNTHESIZED_METHOD_NAME);
+ goto bail;
+ }
+
+ if (jniRegisterNativeMethods(
+ env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
+ goto bail;
+
+ /* success -- return valid version number */
+ result = JNI_VERSION_1_4;
+
+ bail:
+ return result;
+}
diff --git a/vpn/java/android/net/vpn/IVpnService.aidl b/vpn/java/android/net/vpn/IVpnService.aidl
new file mode 100644
index 0000000..0e658df
--- /dev/null
+++ b/vpn/java/android/net/vpn/IVpnService.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+import android.net.vpn.VpnProfile;
+
+/**
+ * Interface to access a VPN service.
+ * {@hide}
+ */
+interface IVpnService {
+ /**
+ * Sets up the VPN connection.
+ * @param profile the profile object
+ * @param username the username for authentication
+ * @param password the corresponding password for authentication
+ */
+ boolean connect(in VpnProfile profile, String username, String password);
+
+ /**
+ * Tears down the VPN connection.
+ */
+ void disconnect();
+
+ /**
+ * Makes the service broadcast the connectivity state.
+ */
+ void checkStatus(in VpnProfile profile);
+}
diff --git a/vpn/java/android/net/vpn/L2tpIpsecProfile.java b/vpn/java/android/net/vpn/L2tpIpsecProfile.java
new file mode 100644
index 0000000..a7e53d1
--- /dev/null
+++ b/vpn/java/android/net/vpn/L2tpIpsecProfile.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+import android.os.Parcel;
+
+/**
+ * The profile for L2TP-over-IPSec type of VPN.
+ * {@hide}
+ */
+public class L2tpIpsecProfile extends SingleServerProfile {
+ private String mUserCertificate;
+ private String mCaCertificate;
+ private String mUserkey;
+
+ @Override
+ public VpnType getType() {
+ return VpnType.L2TP_IPSEC;
+ }
+
+ public void setCaCertificate(String name) {
+ mCaCertificate = name;
+ }
+
+ public String getCaCertificate() {
+ return mCaCertificate;
+ }
+
+ public void setUserCertificate(String name) {
+ mUserCertificate = name;
+ }
+
+ public String getUserCertificate() {
+ return mUserCertificate;
+ }
+
+ public void setUserkey(String name) {
+ mUserkey = name;
+ }
+
+ public String getUserkey() {
+ return mUserkey;
+ }
+
+ @Override
+ protected void readFromParcel(Parcel in) {
+ super.readFromParcel(in);
+ mCaCertificate = in.readString();
+ mUserCertificate = in.readString();
+ mUserkey = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeString(mCaCertificate);
+ parcel.writeString(mUserCertificate);
+ parcel.writeString(mUserkey);
+ }
+}
diff --git a/vpn/java/android/net/vpn/L2tpProfile.java b/vpn/java/android/net/vpn/L2tpProfile.java
new file mode 100644
index 0000000..ca4ef75
--- /dev/null
+++ b/vpn/java/android/net/vpn/L2tpProfile.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+/**
+ * The profile for L2TP type of VPN.
+ * {@hide}
+ */
+public class L2tpProfile extends SingleServerProfile {
+ @Override
+ public VpnType getType() {
+ return VpnType.L2TP;
+ }
+}
diff --git a/vpn/java/android/net/vpn/SingleServerProfile.java b/vpn/java/android/net/vpn/SingleServerProfile.java
new file mode 100644
index 0000000..59b5a7b
--- /dev/null
+++ b/vpn/java/android/net/vpn/SingleServerProfile.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+import android.os.Parcel;
+
+/**
+ * The profile for single-server type of VPN.
+ * {@hide}
+ */
+public abstract class SingleServerProfile extends VpnProfile {
+ private String mServerName;
+
+ public void setServerName(String name) {
+ mServerName = name;
+ }
+
+ public String getServerName() {
+ return mServerName;
+ }
+
+ @Override
+ protected void readFromParcel(Parcel in) {
+ super.readFromParcel(in);
+ mServerName = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeString(mServerName);
+ }
+}
diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java
new file mode 100644
index 0000000..6c6e52a
--- /dev/null
+++ b/vpn/java/android/net/vpn/VpnManager.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.util.Log;
+
+/**
+ * The class provides interface to manage all VPN-related tasks, including:
+ * <ul>
+ * <li>The list of supported VPN types.
+ * <li>API's to start/stop the service of a particular type.
+ * <li>API's to start the settings activity.
+ * <li>API's to create a profile.
+ * <li>API's to register/unregister a connectivity receiver and the keys to
+ * access the fields in a connectivity broadcast event.
+ * </ul>
+ * {@hide}
+ */
+public class VpnManager {
+ // Action for broadcasting a connectivity state.
+ private static final String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";
+ /** Key to the profile name of a connectivity broadcast event. */
+ public static final String BROADCAST_PROFILE_NAME = "profile_name";
+ /** Key to the connectivity state of a connectivity broadcast event. */
+ public static final String BROADCAST_CONNECTION_STATE = "connection_state";
+
+ public static final String PROFILES_PATH = "/data/misc/vpn/profiles";
+
+ private static final String PACKAGE_PREFIX =
+ VpnManager.class.getPackage().getName() + ".";
+
+ /** Action to start the activity of installing a new profile. */
+ public static final String ACTION_VPN_INSTALL_PROFILE =
+ PACKAGE_PREFIX + "INSTALL_PROFILE";
+ /**
+ * Key to the installation path in the intent of installing a new profile.
+ */
+ public static final String KEY_INSTALLATION_PATH = "install_path";
+ public static final String DEFAULT_INSTALLATION_PATH =
+ "/data/local/tmp/vpn";
+
+ // Action to start VPN installation monitor service
+ private static final String SERVICE_VPN_INSTALL_MONITOR =
+ PACKAGE_PREFIX + "INSTALLATION_MONITOR";
+
+ // Action to start VPN settings
+ private static final String ACTION_VPN_SETTINGS = PACKAGE_PREFIX + "SETTINGS";
+
+ private static final String TAG = VpnManager.class.getSimpleName();
+
+ /**
+ * Returns all supported VPN types.
+ */
+ public static VpnType[] getSupportedVpnTypes() {
+ return VpnType.values();
+ }
+
+ private Context mContext;
+
+ /**
+ * Creates a manager object with the specified context.
+ */
+ public VpnManager(Context c) {
+ mContext = c;
+ }
+
+ /**
+ * Creates a VPN profile of the specified type.
+ *
+ * @param type the VPN type
+ * @return the profile object
+ */
+ public VpnProfile createVpnProfile(VpnType type) {
+ return createVpnProfile(type, false);
+ }
+
+ /**
+ * Creates a VPN profile of the specified type.
+ *
+ * @param type the VPN type
+ * @param customized true if the profile is custom made
+ * @return the profile object
+ */
+ public VpnProfile createVpnProfile(VpnType type, boolean customized) {
+ try {
+ VpnProfile p = (VpnProfile) type.getProfileClass().newInstance();
+ p.setCustomized(customized);
+ return p;
+ } catch (InstantiationException e) {
+ return null;
+ } catch (IllegalAccessException e) {
+ return null;
+ }
+ }
+
+ private String getServiceActionName(VpnType type) {
+ return PACKAGE_PREFIX + type.getServiceName();
+ }
+
+ /**
+ * Starts the VPN service of the specified type.
+ */
+ public boolean startService(VpnType type) {
+ String serviceAction = getServiceActionName(type);
+ if (serviceAction != null) {
+ Log.i(TAG, "start service: " + serviceAction);
+ mContext.startService(new Intent(serviceAction));
+ return true;
+ } else {
+ Log.w(TAG, "unknown vpn type to start service for: " + type);
+ return false;
+ }
+ }
+
+ /**
+ * Stops the VPN service of the specified type.
+ */
+ public void stopService(VpnType type) {
+ String serviceAction = getServiceActionName(type);
+ if (serviceAction != null) {
+ Log.i(TAG, "stop service for: " + type);
+ mContext.stopService(new Intent(serviceAction));
+ } else {
+ Log.w(TAG, "unknown vpn type to stop service for: " + type);
+ }
+ }
+
+ /**
+ * Binds the specified ServiceConnection with the VPN service of the
+ * specified type.
+ */
+ public boolean bindService(VpnType type, ServiceConnection c) {
+ String serviceAction = getServiceActionName(type);
+ if (serviceAction == null) {
+ Log.w(TAG, "unknown vpn type to bind service for: " + type);
+ return false;
+ }
+ if (!mContext.bindService(new Intent(serviceAction), c, 0)) {
+ Log.w(TAG, "failed to connect to service: " + type);
+ return false;
+ } else {
+ Log.v(TAG, "succeeded to connect to service: " + type);
+ return true;
+ }
+ }
+
+ /** Broadcasts the connectivity state of the specified profile. */
+ public void broadcastConnectivity(String profileName, VpnState s) {
+ Intent intent = new Intent(ACTION_VPN_CONNECTIVITY);
+ intent.putExtra(BROADCAST_PROFILE_NAME, profileName);
+ intent.putExtra(BROADCAST_CONNECTION_STATE, s);
+ mContext.sendBroadcast(intent);
+ }
+
+ public void registerConnectivityReceiver(BroadcastReceiver r) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(VpnManager.ACTION_VPN_CONNECTIVITY);
+ mContext.registerReceiver(r, filter);
+ }
+
+ public void unregisterConnectivityReceiver(BroadcastReceiver r) {
+ mContext.unregisterReceiver(r);
+ }
+
+ /**
+ * Starts the installation monitor service.
+ * The service monitors the default installtion path (under /data/local/tmp)
+ * and automatically starts the activity to create a new profile when new
+ * configuration files appear in that path.
+ */
+ public void startInstallationMonitorService() {
+ mContext.startService(new Intent(SERVICE_VPN_INSTALL_MONITOR));
+ }
+
+ /** Stops the installation monitor service. */
+ public void stopInstallationMonitorService() {
+ mContext.stopService(new Intent(SERVICE_VPN_INSTALL_MONITOR));
+ }
+
+ /** Starts the VPN settings activity. */
+ public void startSettingsActivity() {
+ Intent intent = new Intent(ACTION_VPN_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ /**
+ * Starts the activity to install a customized profile.
+ * @param installPath the path where all the configuration files are located
+ */
+ public void startInstallProfileActivity(String installPath) {
+ Intent intent = new Intent(ACTION_VPN_INSTALL_PROFILE);
+ intent.putExtra(KEY_INSTALLATION_PATH, installPath);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+}
diff --git a/vpn/java/android/net/vpn/VpnProfile.aidl b/vpn/java/android/net/vpn/VpnProfile.aidl
new file mode 100644
index 0000000..ad34bfc
--- /dev/null
+++ b/vpn/java/android/net/vpn/VpnProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+parcelable VpnProfile;
diff --git a/vpn/java/android/net/vpn/VpnProfile.java b/vpn/java/android/net/vpn/VpnProfile.java
new file mode 100644
index 0000000..7cf2608
--- /dev/null
+++ b/vpn/java/android/net/vpn/VpnProfile.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * A VPN profile.
+ * {@hide}
+ */
+public abstract class VpnProfile implements Parcelable {
+ private String mName; // unique display name
+ private String mId; // unique identifier
+ private String mDomainSuffices; // space separated list
+ private String mRouteList; // space separated list
+ private boolean mIsCustomized;
+ private transient VpnState mState = VpnState.IDLE;
+
+ /** Sets a user-friendly name for this profile. */
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Sets an ID for this profile. The caller should make sure the
+ * uniqueness of the ID.
+ */
+ public void setId(String id) {
+ mId = id;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the domain suffices for DNS resolution.
+ *
+ * @param entries a comma-separated list of domain suffices
+ */
+ public void setDomainSuffices(String entries) {
+ mDomainSuffices = entries;
+ }
+
+ public String getDomainSuffices() {
+ return mDomainSuffices;
+ }
+
+ /**
+ * Sets the routing info for this VPN connection.
+ *
+ * @param entries a comma-separated list of routes; each entry is in the
+ * format of "(network address)/(network mask)"
+ */
+ public void setRouteList(String entries) {
+ mRouteList = entries;
+ }
+
+ public String getRouteList() {
+ return mRouteList;
+ }
+
+ public void setState(VpnState state) {
+ mState = state;
+ }
+
+ public VpnState getState() {
+ return ((mState == null) ? VpnState.IDLE : mState);
+ }
+
+ public boolean isIdle() {
+ return (mState == VpnState.IDLE);
+ }
+
+ /**
+ * Returns whether this profile is custom made (as opposed to being
+ * created by provided user interface).
+ */
+ public boolean isCustomized() {
+ return mIsCustomized;
+ }
+
+ /**
+ * Returns the VPN type of the profile.
+ */
+ public abstract VpnType getType();
+
+ void setCustomized(boolean customized) {
+ mIsCustomized = customized;
+ }
+
+ protected void readFromParcel(Parcel in) {
+ mName = in.readString();
+ mId = in.readString();
+ mDomainSuffices = in.readString();
+ mRouteList = in.readString();
+ }
+
+ public static final Parcelable.Creator<VpnProfile> CREATOR =
+ new Parcelable.Creator<VpnProfile>() {
+ public VpnProfile createFromParcel(Parcel in) {
+ VpnType type = Enum.valueOf(VpnType.class, in.readString());
+ boolean customized = in.readInt() > 0;
+ VpnProfile p = new VpnManager(null).createVpnProfile(type,
+ customized);
+ if (p == null) return null;
+ p.readFromParcel(in);
+ return p;
+ }
+
+ public VpnProfile[] newArray(int size) {
+ return new VpnProfile[size];
+ }
+ };
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(getType().toString());
+ parcel.writeInt(mIsCustomized ? 1 : 0);
+ parcel.writeString(mName);
+ parcel.writeString(mId);
+ parcel.writeString(mDomainSuffices);
+ parcel.writeString(mRouteList);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/vpn/java/android/net/vpn/VpnState.java b/vpn/java/android/net/vpn/VpnState.java
new file mode 100644
index 0000000..78117e0
--- /dev/null
+++ b/vpn/java/android/net/vpn/VpnState.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+/**
+ * Enumeration of all VPN states.
+ *
+ * A normal VPN connection lifetime starts in {@link IDLE}. When a new
+ * connection is about to be set up, it goes to {@link CONNECTING} and then
+ * {@link CONNECTED} if successful; back to {@link IDLE} if failed.
+ * When the connection is about to be torn down, it goes to
+ * {@link DISCONNECTING} and then {@link IDLE}.
+ * {@hide}
+ */
+public enum VpnState {
+ CONNECTING, DISCONNECTING, CONNECTED, IDLE
+}
diff --git a/vpn/java/android/net/vpn/VpnType.java b/vpn/java/android/net/vpn/VpnType.java
new file mode 100644
index 0000000..dcf2078
--- /dev/null
+++ b/vpn/java/android/net/vpn/VpnType.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+/**
+ * Enumeration of all supported VPN types.
+ * {@hide}
+ */
+public enum VpnType {
+ L2TP_IPSEC("L2TP/IPSec", L2tpIpsecProfile.class),
+ L2TP("L2TP", L2tpProfile.class);
+
+ private String mDisplayName;
+ private Class<? extends VpnProfile> mClass;
+
+ VpnType(String displayName, Class<? extends VpnProfile> klass) {
+ mDisplayName = displayName;
+ mClass = klass;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public Class<? extends VpnProfile> getProfileClass() {
+ return mClass;
+ }
+
+ public String getServiceName() {
+ return this.toString();
+ }
+}