summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java57
-rw-r--r--core/java/android/app/BackupAgent.java5
-rw-r--r--core/java/android/backup/BackupHelperAgent.java2
-rw-r--r--core/java/android/backup/BackupHelperDispatcher.java76
-rw-r--r--core/java/android/backup/BackupManager.java16
-rw-r--r--core/java/android/backup/IRestoreObserver.aidl50
-rw-r--r--core/java/android/backup/IRestoreSession.aidl5
-rw-r--r--core/java/android/backup/SharedPreferencesBackupHelper.java2
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl6
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl14
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java8
-rw-r--r--core/jni/Android.mk3
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_backup_BackupHelperDispatcher.cpp251
-rw-r--r--core/jni/android_util_Process.cpp4
-rw-r--r--include/tts/TtsEngine.h41
-rw-r--r--packages/TtsService/jni/android_tts_SynthProxy.cpp48
-rwxr-xr-xpackages/TtsService/src/android/tts/SynthProxy.java10
-rwxr-xr-xpackages/TtsService/src/android/tts/TtsService.java167
-rw-r--r--services/java/com/android/server/BackupManagerService.java136
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java9
-rw-r--r--telephony/java/com/android/internal/telephony/SMSDispatcher.java1
-rw-r--r--telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java22
-rw-r--r--tests/backup/src/com/android/backuptest/BackupTestAgent.java4
-rwxr-xr-xtests/backup/test_backup.sh27
-rwxr-xr-xtests/backup/test_restore.sh53
27 files changed, 915 insertions, 105 deletions
diff --git a/Android.mk b/Android.mk
index 0e8793d..f32129e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -82,6 +82,7 @@ LOCAL_SRC_FILES += \
core/java/android/app/IWallpaperService.aidl \
core/java/android/app/IWallpaperServiceCallback.aidl \
core/java/android/backup/IBackupManager.aidl \
+ core/java/android/backup/IRestoreObserver.aidl \
core/java/android/backup/IRestoreSession.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \
core/java/android/bluetooth/IBluetoothDevice.aidl \
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 841e3df..68caa26 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -17,6 +17,7 @@
package com.android.commands.bmgr;
import android.backup.IBackupManager;
+import android.backup.IRestoreObserver;
import android.backup.IRestoreSession;
import android.backup.RestoreSet;
import android.os.RemoteException;
@@ -170,9 +171,7 @@ public final class Bmgr {
if (sets == null || sets.length == 0) {
System.out.println("No restore sets available");
} else {
- for (RestoreSet s : sets) {
- System.out.println(" " + s.token + " : " + s.name);
- }
+ printRestoreSets(sets);
}
} catch (RemoteException e) {
System.err.println(e.toString());
@@ -180,6 +179,31 @@ public final class Bmgr {
}
}
+ private void printRestoreSets(RestoreSet[] sets) {
+ for (RestoreSet s : sets) {
+ System.out.println(" " + s.token + " : " + s.name);
+ }
+ }
+
+ class RestoreObserver extends IRestoreObserver.Stub {
+ boolean done;
+ public void restoreStarting(int numPackages) {
+ System.out.println("restoreStarting: " + numPackages + " packages");
+ }
+
+ public void onUpdate(int nowBeingRestored) {
+ System.out.println("onUpdate: " + nowBeingRestored);
+ }
+
+ public void restoreFinished(int error) {
+ System.out.println("restoreFinished: " + error);
+ synchronized (this) {
+ done = true;
+ this.notify();
+ }
+ }
+ }
+
private void doRestore() {
int token;
try {
@@ -189,7 +213,10 @@ public final class Bmgr {
return;
}
+ RestoreObserver observer = new RestoreObserver();
+
try {
+ boolean didRestore = false;
int curTransport = mBmgr.getCurrentTransport();
mRestore = mBmgr.beginRestoreSession(curTransport);
if (mRestore == null) {
@@ -200,15 +227,35 @@ public final class Bmgr {
for (RestoreSet s : sets) {
if (s.token == token) {
System.out.println("Scheduling restore: " + s.name);
- mRestore.performRestore(token);
+ mRestore.performRestore(token, observer);
+ didRestore = true;
break;
}
}
+ if (!didRestore) {
+ if (sets == null || sets.length == 0) {
+ System.out.println("No available restore sets; no restore performed");
+ } else {
+ System.out.println("No matching restore set token. Available sets:");
+ printRestoreSets(sets);
+ }
+ }
mRestore.endRestoreSession();
} catch (RemoteException e) {
System.err.println(e.toString());
System.err.println(BMGR_NOT_RUNNING_ERR);
}
+
+ // now wait for it to be done
+ synchronized (observer) {
+ while (!observer.done) {
+ try {
+ observer.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ System.out.println("done");
}
private String nextArg() {
@@ -229,4 +276,4 @@ public final class Bmgr {
System.err.println(" bmgr restore token#");
System.err.println(" bmgr run");
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java
index e810775..0ac8a1e 100644
--- a/core/java/android/app/BackupAgent.java
+++ b/core/java/android/app/BackupAgent.java
@@ -67,7 +67,7 @@ public abstract class BackupAgent extends ContextWrapper {
* here after writing the requested data to dataFd.
*/
public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState);
+ ParcelFileDescriptor newState) throws IOException;
/**
* The application is being restored from backup, and should replace any
@@ -120,6 +120,9 @@ public abstract class BackupAgent extends ContextWrapper {
BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
try {
BackupAgent.this.onBackup(oldState, output, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
} catch (RuntimeException ex) {
Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
index 3720d50..5d0c4a2 100644
--- a/core/java/android/backup/BackupHelperAgent.java
+++ b/core/java/android/backup/BackupHelperAgent.java
@@ -34,7 +34,7 @@ public class BackupHelperAgent extends BackupAgent {
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
+ ParcelFileDescriptor newState) throws IOException {
mDispatcher.performBackup(oldState, data, newState);
}
diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java
index b25c3e3..6ccb83e 100644
--- a/core/java/android/backup/BackupHelperDispatcher.java
+++ b/core/java/android/backup/BackupHelperDispatcher.java
@@ -19,7 +19,10 @@ package android.backup;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.IOException;
+import java.io.FileDescriptor;
import java.util.TreeMap;
import java.util.Map;
@@ -27,6 +30,11 @@ import java.util.Map;
public class BackupHelperDispatcher {
private static final String TAG = "BackupHelperDispatcher";
+ private static class Header {
+ int chunkSize; // not including the header
+ String keyPrefix;
+ }
+
TreeMap<String,BackupHelper> mHelpers = new TreeMap<String,BackupHelper>();
public BackupHelperDispatcher() {
@@ -36,13 +44,63 @@ public class BackupHelperDispatcher {
mHelpers.put(keyPrefix, helper);
}
- /** TODO: Make this save and restore the key prefix. */
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
- // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
- for (Map.Entry<String,BackupHelper> entry: mHelpers.entrySet()) {
- data.setKeyPrefix(entry.getKey());
- entry.getValue().performBackup(oldState, data, newState);
+ ParcelFileDescriptor newState) throws IOException {
+ // First, do the helpers that we've already done, since they're already in the state
+ // file.
+ int err;
+ Header header = new Header();
+ TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
+ FileDescriptor oldStateFD = null;
+ FileDescriptor newStateFD = newState.getFileDescriptor();
+
+ if (oldState != null) {
+ oldStateFD = oldState.getFileDescriptor();
+ while ((err = readHeader_native(header, oldStateFD)) >= 0) {
+ if (err == 0) {
+ BackupHelper helper = helpers.get(header.keyPrefix);
+ Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
+ if (helper != null) {
+ doOneBackup(oldState, data, newState, header, helper);
+ helpers.remove(header.keyPrefix);
+ } else {
+ skipChunk_native(oldStateFD, header.chunkSize);
+ }
+ }
+ }
+ }
+
+ // Then go through and do the rest that we haven't done.
+ for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) {
+ header.keyPrefix = entry.getKey();
+ Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
+ BackupHelper helper = entry.getValue();
+ doOneBackup(oldState, data, newState, header, helper);
+ }
+ }
+
+ private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState, Header header, BackupHelper helper)
+ throws IOException {
+ int err;
+ FileDescriptor newStateFD = newState.getFileDescriptor();
+
+ // allocate space for the header in the file
+ int pos = allocateHeader_native(header, newStateFD);
+ if (pos < 0) {
+ throw new IOException("allocateHeader_native failed (error " + pos + ")");
+ }
+
+ data.setKeyPrefix(header.keyPrefix);
+
+ // do the backup
+ helper.performBackup(oldState, data, newState);
+
+ // fill in the header (seeking back to pos). The file pointer will be returned to
+ // where it was at the end of performBackup. Header.chunkSize will not be filled in.
+ err = writeHeader_native(header, newStateFD, pos);
+ if (err != 0) {
+ throw new IOException("writeHeader_native failed (error " + err + ")");
}
}
@@ -83,5 +141,11 @@ public class BackupHelperDispatcher {
helper.writeRestoreSnapshot(newState);
}
}
+
+ private static native int readHeader_native(Header h, FileDescriptor fd);
+ private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
+
+ private static native int allocateHeader_native(Header h, FileDescriptor fd);
+ private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
}
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 8df7eae..79e2c03 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -68,9 +68,11 @@ public class BackupManager {
* {@link android.app.BackupAgent} subclass will be scheduled when you call this method.
*/
public void dataChanged() {
- try {
- mService.dataChanged(mContext.getPackageName());
- } catch (RemoteException e) {
+ if (mService != null) {
+ try {
+ mService.dataChanged(mContext.getPackageName());
+ } catch (RemoteException e) {
+ }
}
}
@@ -83,9 +85,11 @@ public class BackupManager {
*/
public IRestoreSession beginRestoreSession(int transportID) {
IRestoreSession binder = null;
- try {
- binder = mService.beginRestoreSession(transportID);
- } catch (RemoteException e) {
+ if (mService != null) {
+ try {
+ binder = mService.beginRestoreSession(transportID);
+ } catch (RemoteException e) {
+ }
}
return binder;
}
diff --git a/core/java/android/backup/IRestoreObserver.aidl b/core/java/android/backup/IRestoreObserver.aidl
new file mode 100644
index 0000000..59e59fc
--- /dev/null
+++ b/core/java/android/backup/IRestoreObserver.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.backup;
+
+/**
+ * Callback class for receiving progress reports during a restore operation.
+ *
+ * @hide
+ */
+interface IRestoreObserver {
+ /**
+ * The restore operation has begun.
+ *
+ * @param numPackages The total number of packages being processed in
+ * this restore operation.
+ */
+ void restoreStarting(int numPackages);
+
+ /**
+ * An indication of which package is being restored currently, out of the
+ * total number provided in the restoreStarting() callback. This method
+ * is not guaranteed to be called.
+ *
+ * @param nowBeingRestored The index, between 1 and the numPackages parameter
+ * to the restoreStarting() callback, of the package now being restored.
+ */
+ void onUpdate(int nowBeingRestored);
+
+ /**
+ * The restore operation has completed.
+ *
+ * @param error Zero on success; a nonzero error code if the restore operation
+ * as a whole failed.
+ */
+ void restoreFinished(int error);
+}
diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/backup/IRestoreSession.aidl
index 6bca865..ac01c2d 100644
--- a/core/java/android/backup/IRestoreSession.aidl
+++ b/core/java/android/backup/IRestoreSession.aidl
@@ -17,6 +17,7 @@
package android.backup;
import android.backup.RestoreSet;
+import android.backup.IRestoreObserver;
/**
* Binder interface used by clients who wish to manage a restore operation. Every
@@ -41,8 +42,10 @@ interface IRestoreSession {
*
* @param token The token from {@link getAvailableRestoreSets()} corresponding to
* the restore set that should be used.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
*/
- int performRestore(int token);
+ int performRestore(int token, IRestoreObserver observer);
/**
* End this restore session. After this method is called, the IRestoreSession binder
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
index f492629..4a7b399 100644
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -30,7 +30,7 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen
private Context mContext;
private String[] mPrefGroups;
- public SharedPreferencesBackupHelper(Context context, String[] prefGroups) {
+ public SharedPreferencesBackupHelper(Context context, String... prefGroups) {
super(context);
mContext = context;
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
index 75c3b30..47976e5 100755
--- a/core/java/android/speech/tts/ITts.aidl
+++ b/core/java/android/speech/tts/ITts.aidl
@@ -33,6 +33,8 @@ interface ITts {
void speak(in String text, in int queueMode, in String[] params);
+ void speakIpa(in String ipaText, in int queueMode, in String[] params);
+
boolean isSpeaking();
void stop();
@@ -43,7 +45,9 @@ interface ITts {
void setLanguage(in String language, in String country, in String variant);
- boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
+ boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
+
+ boolean synthesizeIpaToFile(in String ipaText, in String[] params, in String outputDirectory);
void playEarcon(in String earcon, in int queueMode, in String[] params);
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index ec63528..4bef265 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -41,6 +41,20 @@ interface IBackupTransport {
- adb: close the file
*/
/**
+ * Ask the transport where, on local device storage, to keep backup state blobs.
+ * This is per-transport so that mock transports used for testing can coexist with
+ * "live" backup services without interfering with the live bookkeeping. The
+ * returned string should be a name that is expected to be unambiguous among all
+ * available backup transports; the name of the class implementing the transport
+ * is a good choice.
+ *
+ * @return A unique name, suitable for use as a file or directory name, that the
+ * Backup Manager could use to disambiguate state files associated with
+ * different backup transports.
+ */
+ String transportDirName();
+
+ /**
* Verify that this is a suitable time for a backup pass. This should return zero
* if a backup is reasonable right now, some positive value otherwise. This method
* will be called outside of the {@link #startSession}/{@link #endSession} pair.
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 0fbbb3f..c5d9d40 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -30,6 +30,9 @@ public class LocalTransport extends IBackupTransport.Stub {
private static final String TAG = "LocalTransport";
private static final boolean DEBUG = true;
+ private static final String TRANSPORT_DIR_NAME
+ = "com.android.internal.backup.LocalTransport";
+
private Context mContext;
private PackageManager mPackageManager;
private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
@@ -43,6 +46,11 @@ public class LocalTransport extends IBackupTransport.Stub {
mPackageManager = context.getPackageManager();
}
+
+ public String transportDirName() throws RemoteException {
+ return TRANSPORT_DIR_NAME;
+ }
+
public long requestBackupTime() throws RemoteException {
// any time is a good time for local backup
return 0;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index b328869..888cb11 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -120,7 +120,8 @@ LOCAL_SRC_FILES:= \
com_android_internal_graphics_NativeUtils.cpp \
android_backup_BackupDataInput.cpp \
android_backup_BackupDataOutput.cpp \
- android_backup_FileBackupHelperBase.cpp
+ android_backup_FileBackupHelperBase.cpp \
+ android_backup_BackupHelperDispatcher.cpp
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7350348..c815301 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -158,6 +158,7 @@ extern int register_android_location_GpsLocationProvider(JNIEnv* env);
extern int register_android_backup_BackupDataInput(JNIEnv *env);
extern int register_android_backup_BackupDataOutput(JNIEnv *env);
extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
+extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env);
static AndroidRuntime* gCurRuntime = NULL;
@@ -1131,6 +1132,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
REG_JNI(register_android_backup_FileBackupHelperBase),
+ REG_JNI(register_android_backup_BackupHelperDispatcher),
};
/*
diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp
new file mode 100644
index 0000000..24d529b
--- /dev/null
+++ b/core/jni/android_backup_BackupHelperDispatcher.cpp
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BackupHelperDispatcher_native"
+#include <utils/Log.h>
+
+#include "JNIHelp.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+
+#define VERSION_1_HEADER 0x01706c48 // 'Hlp'1 little endian
+
+namespace android
+{
+
+struct chunk_header_v1 {
+ int headerSize;
+ int version;
+ int dataSize; // corresponds to Header.chunkSize
+ int nameLength; // not including the NULL terminator, which is not written to the file
+};
+
+// java.io.FileDescriptor
+static jfieldID s_descriptorField = 0;
+static jfieldID s_chunkSizeField = 0;
+static jfieldID s_keyPrefixField = 0;
+
+static int
+readHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj)
+{
+ chunk_header_v1 flattenedHeader;
+ int fd;
+ ssize_t amt;
+ String8 keyPrefix;
+ char* buf;
+
+ fd = env->GetIntField(fdObj, s_descriptorField);
+
+ amt = read(fd, &flattenedHeader.headerSize, sizeof(flattenedHeader.headerSize));
+ if (amt != sizeof(flattenedHeader.headerSize)) {
+ return -1;
+ }
+
+ int remainingHeader = flattenedHeader.headerSize - sizeof(flattenedHeader.headerSize);
+
+ if (flattenedHeader.headerSize < (int)sizeof(chunk_header_v1)) {
+ LOGW("Skipping unknown header: %d bytes", flattenedHeader.headerSize);
+ if (remainingHeader > 0) {
+ lseek(fd, remainingHeader, SEEK_CUR);
+ // >0 means skip this chunk
+ return 1;
+ }
+ }
+
+ amt = read(fd, &flattenedHeader.version,
+ sizeof(chunk_header_v1)-sizeof(flattenedHeader.headerSize));
+ if (amt <= 0) {
+ LOGW("Failed reading chunk header");
+ return -1;
+ }
+ remainingHeader -= sizeof(chunk_header_v1)-sizeof(flattenedHeader.headerSize);
+
+ if (flattenedHeader.version != VERSION_1_HEADER) {
+ LOGW("Skipping unknown header version: 0x%08x, %d bytes", flattenedHeader.version,
+ flattenedHeader.headerSize);
+ if (remainingHeader > 0) {
+ lseek(fd, remainingHeader, SEEK_CUR);
+ // >0 means skip this chunk
+ return 1;
+ }
+ }
+
+ if (flattenedHeader.dataSize < 0 || flattenedHeader.nameLength < 0 ||
+ remainingHeader < flattenedHeader.nameLength) {
+ LOGW("Malformed V1 header remainingHeader=%d dataSize=%d nameLength=%d", remainingHeader,
+ flattenedHeader.dataSize, flattenedHeader.nameLength);
+ return -1;
+ }
+
+ buf = keyPrefix.lockBuffer(flattenedHeader.nameLength);
+ if (buf == NULL) {
+ LOGW("unable to allocate %d bytes", flattenedHeader.nameLength);
+ return -1;
+ }
+
+ amt = read(fd, buf, flattenedHeader.nameLength);
+
+ keyPrefix.unlockBuffer(flattenedHeader.nameLength);
+
+ remainingHeader -= flattenedHeader.nameLength;
+
+ LOGD("remainingHeader=%d", remainingHeader);
+
+ if (remainingHeader > 0) {
+ lseek(fd, remainingHeader, SEEK_CUR);
+ }
+
+ env->SetIntField(headerObj, s_chunkSizeField, flattenedHeader.dataSize);
+ env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.string()));
+
+ return 0;
+}
+
+static int
+skipChunk_native(JNIEnv* env, jobject clazz, jobject fdObj, jint bytesToSkip)
+{
+ int fd;
+
+ fd = env->GetIntField(fdObj, s_descriptorField);
+
+ lseek(fd, bytesToSkip, SEEK_CUR);
+
+ return 0;
+}
+
+static int
+padding_len(int len)
+{
+ len = len % 4;
+ return len == 0 ? len : 4 - len;
+}
+
+static int
+allocateHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj)
+{
+ int pos;
+ jstring nameObj;
+ int nameLength;
+ int namePadding;
+ int headerSize;
+ int fd;
+
+ fd = env->GetIntField(fdObj, s_descriptorField);
+
+ nameObj = (jstring)env->GetObjectField(headerObj, s_keyPrefixField);
+
+ nameLength = env->GetStringUTFLength(nameObj);
+ namePadding = padding_len(nameLength);
+
+ headerSize = sizeof(chunk_header_v1) + nameLength + namePadding;
+
+ pos = lseek(fd, 0, SEEK_CUR);
+
+ lseek(fd, headerSize, SEEK_CUR);
+
+ return pos;
+}
+
+static int
+writeHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj, jint pos)
+{
+ int err;
+ chunk_header_v1 header;
+ int fd;
+ int namePadding;
+ int prevPos;
+ jstring nameObj;
+ const char* buf;
+
+ fd = env->GetIntField(fdObj, s_descriptorField);
+ prevPos = lseek(fd, 0, SEEK_CUR);
+
+ nameObj = (jstring)env->GetObjectField(headerObj, s_keyPrefixField);
+ header.nameLength = env->GetStringUTFLength(nameObj);
+ namePadding = padding_len(header.nameLength);
+
+ header.headerSize = sizeof(chunk_header_v1) + header.nameLength + namePadding;
+ header.version = VERSION_1_HEADER;
+
+ lseek(fd, pos, SEEK_SET);
+ err = write(fd, &header, sizeof(chunk_header_v1));
+ if (err != sizeof(chunk_header_v1)) {
+ return errno;
+ }
+
+ buf = env->GetStringUTFChars(nameObj, NULL);
+ err = write(fd, buf, header.nameLength);
+ env->ReleaseStringUTFChars(nameObj, buf);
+ if (err != header.nameLength) {
+ return errno;
+ }
+
+ if (namePadding != 0) {
+ int zero = 0;
+ err = write(fd, &zero, namePadding);
+ if (err != namePadding) {
+ return errno;
+ }
+ }
+
+ lseek(fd, prevPos, SEEK_SET);
+ return 0;
+}
+
+static const JNINativeMethod g_methods[] = {
+ { "readHeader_native",
+ "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
+ (void*)readHeader_native },
+ { "skipChunk_native",
+ "(Ljava/io/FileDescriptor;I)I",
+ (void*)skipChunk_native },
+ { "allocateHeader_native",
+ "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
+ (void*)allocateHeader_native },
+ { "writeHeader_native",
+ "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;I)I",
+ (void*)writeHeader_native },
+};
+
+int register_android_backup_BackupHelperDispatcher(JNIEnv* env)
+{
+ jclass clazz;
+
+ clazz = env->FindClass("java/io/FileDescriptor");
+ LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
+ s_descriptorField = env->GetFieldID(clazz, "descriptor", "I");
+ LOG_FATAL_IF(s_descriptorField == NULL,
+ "Unable to find descriptor field in java.io.FileDescriptor");
+
+ clazz = env->FindClass("android/backup/BackupHelperDispatcher$Header");
+ LOG_FATAL_IF(clazz == NULL,
+ "Unable to find class android.backup.BackupHelperDispatcher.Header");
+ s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I");
+ LOG_FATAL_IF(s_chunkSizeField == NULL,
+ "Unable to find chunkSize field in android.backup.BackupHelperDispatcher.Header");
+ s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;");
+ LOG_FATAL_IF(s_keyPrefixField == NULL,
+ "Unable to find keyPrefix field in android.backup.BackupHelperDispatcher.Header");
+
+ return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupHelperDispatcher",
+ g_methods, NELEM(g_methods));
+}
+
+}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index c890b0f..aee0ed7 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -269,9 +269,9 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin
void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
jint pid, jint pri)
{
- if (pri == ANDROID_PRIORITY_BACKGROUND) {
+ if (pri >= ANDROID_PRIORITY_BACKGROUND) {
add_pid_to_cgroup(pid, ANDROID_TGROUP_BG_NONINTERACT);
- } else if (getpriority(PRIO_PROCESS, pid) == ANDROID_PRIORITY_BACKGROUND) {
+ } else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
add_pid_to_cgroup(pid, ANDROID_TGROUP_DEFAULT);
}
diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h
index 8486532..ca50a5e 100644
--- a/include/tts/TtsEngine.h
+++ b/include/tts/TtsEngine.h
@@ -69,6 +69,14 @@ enum tts_result {
TTS_MISSING_RESOURCES = -6
};
+enum tts_support_result {
+ TTS_LANG_COUNTRY_VAR_AVAILABLE = 2,
+ TTS_LANG_COUNTRY_AVAILABLE = 1,
+ TTS_LANG_AVAILABLE = 0,
+ TTS_LANG_MISSING_DATA = -1,
+ TTS_LANG_NOT_SUPPORTED = -2
+};
+
class TtsEngine
{
public:
@@ -86,19 +94,32 @@ public:
// @return TTS_SUCCESS, or TTS_FAILURE
virtual tts_result stop();
+ // Returns the level of support for the language, country and variant.
+ // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported,
+ // and the corresponding resources are correctly installed
+ // TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the
+ // corresponding resources are correctly installed, but there is no match for
+ // the specified variant
+ // TTS_LANG_AVAILABLE if the language is supported and the
+ // corresponding resources are correctly installed, but there is no match for
+ // the specified country and variant
+ // TTS_LANG_MISSING_DATA if the required resources to provide any level of support
+ // for the language are not correctly installed
+ // TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine.
+ virtual tts_support_result isLanguageAvailable(const char *lang, const char *country,
+ const char *variant);
+
// 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
+ // language value is issued. Language and country values are coded according to the ISO three
+ // letter codes for languages and countries, as can be retrieved from a java.util.Locale
+ // instance. The variant value is encoded as the variant string retrieved from a
+ // java.util.Locale instance built with that variant data.
+ // @param lang pointer to the ISO three letter code for the language
+ // @param country pointer to the ISO three letter code for the country
+ // @param variant pointer to the variant code
// @return TTS_SUCCESS, or TTS_FAILURE
- virtual tts_result loadLanguage(const char *value, const size_t size);
+ virtual tts_result loadLanguage(const char *lang, const char *country, const char *variant);
// Load the resources associated with the specified language, country and Locale variant.
// The loaded language will only be used once a call to setLanguageFromLocale() with the same
diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp
index 0dafcc1..8537cae 100644
--- a/packages/TtsService/jni/android_tts_SynthProxy.cpp
+++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp
@@ -204,11 +204,19 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,
fwrite(wav, 1, bufferSize, pForAfter->outputFile);
}
}
- // TODO update to call back into the SynthProxy class through the
+ // Future update:
+ // For sync points in the speech, 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;
+ // playback has completed if the synthesis is done or if a marker has been reached.
+
+ if (status == TTS_SYNTH_DONE) {
+ // this struct was allocated in the original android_tts_SynthProxy_speak call,
+ // all processing matching this call is now done.
+ LOGV("Speech synthesis done.");
+ delete pForAfter;
+ pForAfter = NULL;
+ return TTS_CALLBACK_HALT;
+ }
// 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
@@ -289,8 +297,32 @@ android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
variantNativeString);
}
env->ReleaseStringUTFChars(language, langNativeString);
- env->ReleaseStringUTFChars(language, countryNativeString);
- env->ReleaseStringUTFChars(language, variantNativeString);
+ env->ReleaseStringUTFChars(country, countryNativeString);
+ env->ReleaseStringUTFChars(variant, variantNativeString);
+}
+
+
+static void
+android_tts_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData,
+ jstring language, jstring country, jstring variant)
+{
+ if (jniData == 0) {
+ LOGE("android_tts_SynthProxy_loadLanguage(): invalid JNI data");
+ return;
+ }
+
+ SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ const char *langNativeString = env->GetStringUTFChars(language, 0);
+ const char *countryNativeString = env->GetStringUTFChars(country, 0);
+ const char *variantNativeString = env->GetStringUTFChars(variant, 0);
+ // TODO check return codes
+ if (pSynthData->mNativeSynthInterface) {
+ pSynthData->mNativeSynthInterface->loadLanguage(langNativeString, countryNativeString,
+ variantNativeString);
+ }
+ env->ReleaseStringUTFChars(language, langNativeString);
+ env->ReleaseStringUTFChars(country, countryNativeString);
+ env->ReleaseStringUTFChars(variant, variantNativeString);
}
@@ -559,6 +591,10 @@ static JNINativeMethod gMethods[] = {
"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
(void*)android_tts_SynthProxy_setLanguage
},
+ { "native_loadLanguage",
+ "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+ (void*)android_tts_SynthProxy_loadLanguage
+ },
{ "native_setSpeechRate",
"(II)V",
(void*)android_tts_SynthProxy_setSpeechRate
diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java
index 3bdff37..a8eaaa4 100755
--- a/packages/TtsService/src/android/tts/SynthProxy.java
+++ b/packages/TtsService/src/android/tts/SynthProxy.java
@@ -73,6 +73,13 @@ public class SynthProxy {
public void setLanguage(String language, String country, String variant) {
native_setLanguage(mJniData, language, country, variant);
}
+
+ /**
+ * Loads the language: it's not set, but prepared for use later.
+ */
+ public void loadLanguage(String language, String country, String variant) {
+ native_loadLanguage(mJniData, language, country, variant);
+ }
/**
* Sets the speech rate
@@ -149,6 +156,9 @@ public class SynthProxy {
private native final void native_setLanguage(int jniData, String language, String country,
String variant);
+
+ private native final void native_loadLanguage(int jniData, String language, String country,
+ String variant);
private native final void native_setSpeechRate(int jniData, int speechRate);
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
index 6e6e121..421b2ca 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -47,12 +47,13 @@ import java.util.concurrent.locks.ReentrantLock;
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 static final int TEXT = 0;
+ public static final int IPA = 1;
+ public static final int EARCON = 2;
+ public static final int SILENCE = 3;
public String mText = null;
public ArrayList<String> mParams = null;
- public int mType = SPEECH;
+ public int mType = TEXT;
public long mDuration = 0;
public SpeechItem(String text, ArrayList<String> params, int itemType) {
@@ -89,6 +90,8 @@ public class TtsService extends Service implements OnCompletionListener {
}
}
+ private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
+
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";
@@ -108,7 +111,6 @@ public class TtsService extends Service implements OnCompletionListener {
private final ReentrantLock synthesizerLock = new ReentrantLock();
private SynthProxy nativeSynth;
-
@Override
public void onCreate() {
super.onCreate();
@@ -145,13 +147,11 @@ public class TtsService extends Service implements OnCompletionListener {
private void setDefaultSettings() {
-
// TODO handle default language
setLanguage("eng", "USA", "");
// speech rate
setSpeechRate(getDefaultRate());
-
}
@@ -298,7 +298,29 @@ public class TtsService extends Service implements OnCompletionListener {
if (queueMode == 0) {
stop();
}
- mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH));
+ mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.TEXT));
+ if (!mIsSpeaking) {
+ processSpeechQueue();
+ }
+ }
+
+ /**
+ * Speaks the given IPA text using the specified queueing mode and parameters.
+ *
+ * @param ipaText
+ * The IPA 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 speakIpa(String ipaText, int queueMode, ArrayList<String> params) {
+ if (queueMode == 0) {
+ stop();
+ }
+ mSpeechQueue.add(new SpeechItem(ipaText, params, SpeechItem.IPA));
if (!mIsSpeaking) {
processSpeechQueue();
}
@@ -445,6 +467,33 @@ public class TtsService extends Service implements OnCompletionListener {
Log.i("TTS callback", "dispatch completed to " + N);
}
+ private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){
+ if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){
+ return currentSpeechItem;
+ } else {
+ ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>();
+ int start = 0;
+ int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
+ String splitText;
+ SpeechItem splitItem;
+ while (end < currentSpeechItem.mText.length()){
+ splitText = currentSpeechItem.mText.substring(start, end);
+ splitItem = new SpeechItem(splitText, null, SpeechItem.TEXT);
+ splitItems.add(splitItem);
+ start = end;
+ end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
+ }
+ splitText = currentSpeechItem.mText.substring(start);
+ splitItem = new SpeechItem(splitText, null, SpeechItem.TEXT);
+ splitItems.add(splitItem);
+ mSpeechQueue.remove(0);
+ for (int i = splitItems.size() - 1; i >= 0; i--){
+ mSpeechQueue.add(0, splitItems.get(i));
+ }
+ return mSpeechQueue.get(0);
+ }
+ }
+
private void processSpeechQueue() {
boolean speechQueueAvailable = false;
try {
@@ -465,11 +514,12 @@ public class TtsService extends Service implements OnCompletionListener {
// 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.
+ if (currentSpeechItem.mType == SpeechItem.TEXT) {
+ currentSpeechItem = splitCurrentTextIfNeeded(currentSpeechItem);
speakInternalOnly(currentSpeechItem.mText,
currentSpeechItem.mParams);
+ } else if (currentSpeechItem.mType == SpeechItem.IPA) {
+ // TODO Implement IPA support
} else {
// This is either silence or an earcon that was missing
silence(currentSpeechItem.mDuration);
@@ -535,8 +585,7 @@ public class TtsService extends Service implements OnCompletionListener {
}
/**
- * Synthesizes the given text using the specified queuing mode and
- * parameters.
+ * Synthesizes the given text to a file using the specified parameters.
*
* @param text
* The String of text that should be synthesized
@@ -581,6 +630,52 @@ public class TtsService extends Service implements OnCompletionListener {
return true;
}
+ /**
+ * Synthesizes the given IPA text to a file using the specified parameters.
+ *
+ * @param ipaText
+ * The String of IPA 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 synthesizeIpaToFile(String ipaText, 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 IPA 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;
+ }
+ // TODO: Add nativeSynth.synthesizeIpaToFile(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())) {
@@ -627,6 +722,27 @@ public class TtsService extends Service implements OnCompletionListener {
}
/**
+ * Speaks the given IPA text using the specified queueing mode and
+ * parameters.
+ *
+ * @param ipaText
+ * The IPA 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 speakIpa(String ipaText, int queueMode, String[] params) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ mSelf.speakIpa(ipaText, queueMode, speakingParams);
+ }
+
+ /**
* Plays the earcon using the specified queueing mode and parameters.
*
* @param earcon
@@ -769,7 +885,7 @@ public class TtsService extends Service implements OnCompletionListener {
}
/**
- * Speaks the given text using the specified queueing mode and
+ * Synthesizes the given text to a file using the specified
* parameters.
*
* @param text
@@ -790,6 +906,29 @@ public class TtsService extends Service implements OnCompletionListener {
}
return mSelf.synthesizeToFile(text, speakingParams, filename, true);
}
+
+ /**
+ * Synthesizes the given IPA text to a file using the specified
+ * parameters.
+ *
+ * @param ipaText
+ * The String of IPA 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 synthesizeIpaToFile(String ipaText, String[] params,
+ String filename) {
+ ArrayList<String> speakingParams = new ArrayList<String>();
+ if (params != null) {
+ speakingParams = new ArrayList<String>(Arrays.asList(params));
+ }
+ return mSelf.synthesizeIpaToFile(ipaText, speakingParams, filename, true);
+ }
};
}
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a6639de..bc2eaed 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -47,6 +47,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.backup.IBackupManager;
+import android.backup.IRestoreObserver;
import android.backup.IRestoreSession;
import android.backup.BackupManager;
import android.backup.RestoreSet;
@@ -81,6 +82,7 @@ class BackupManagerService extends IBackupManager.Stub {
private static final int MSG_RUN_BACKUP = 1;
private static final int MSG_RUN_FULL_BACKUP = 2;
private static final int MSG_RUN_RESTORE = 3;
+ private static final String RESTORE_OBSERVER_KEY = "_resOb";
// Timeout interval for deciding that a bind or clear-data has taken too long
static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -109,8 +111,8 @@ class BackupManagerService extends IBackupManager.Stub {
// Backups that we haven't started yet.
private HashMap<ApplicationInfo,BackupRequest> mPendingBackups
= new HashMap<ApplicationInfo,BackupRequest>();
- // Do we need to back up the package manager metadata on the next pass?
- private boolean mDoPackageManager;
+
+ // Pseudoname that we use for the Package Manager metadata "package"
private static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
// locking around the pending-backup management
@@ -133,7 +135,18 @@ class BackupManagerService extends IBackupManager.Stub {
private IBackupTransport mLocalTransport, mGoogleTransport;
private RestoreSession mActiveRestoreSession;
- private File mStateDir;
+ private class RestoreParams {
+ public IBackupTransport transport;
+ public IRestoreObserver observer;
+
+ RestoreParams(IBackupTransport _transport, IRestoreObserver _obs) {
+ transport = _transport;
+ observer = _obs;
+ }
+ }
+
+ // Where we keep our journal files and other bookkeeping
+ private File mBaseStateDir;
private File mDataDir;
private File mJournalDir;
private File mJournal;
@@ -145,13 +158,12 @@ class BackupManagerService extends IBackupManager.Stub {
mActivityManager = ActivityManagerNative.getDefault();
// Set up our bookkeeping
- mStateDir = new File(Environment.getDataDirectory(), "backup");
- mStateDir.mkdirs();
+ mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
mDataDir = Environment.getDownloadCacheDirectory();
// Set up the backup-request journaling
- mJournalDir = new File(mStateDir, "pending");
- mJournalDir.mkdirs();
+ mJournalDir = new File(mBaseStateDir, "pending");
+ mJournalDir.mkdirs(); // creates mBaseStateDir along the way
makeJournalLocked(); // okay because no other threads are running yet
// Build our mapping of uid to backup client services. This implicitly
@@ -336,8 +348,8 @@ class BackupManagerService extends IBackupManager.Stub {
case MSG_RUN_RESTORE:
{
int token = msg.arg1;
- IBackupTransport transport = (IBackupTransport)msg.obj;
- (new PerformRestoreThread(transport, token)).start();
+ RestoreParams params = (RestoreParams)msg.obj;
+ (new PerformRestoreThread(params.transport, params.observer, token)).start();
break;
}
}
@@ -372,7 +384,6 @@ class BackupManagerService extends IBackupManager.Stub {
mBackupParticipants.put(uid, set);
}
set.add(pkg.applicationInfo);
- backUpPackageManagerData();
}
}
}
@@ -416,7 +427,6 @@ class BackupManagerService extends IBackupManager.Stub {
for (ApplicationInfo entry: set) {
if (entry.packageName.equals(pkg.packageName)) {
set.remove(entry);
- backUpPackageManagerData();
break;
}
}
@@ -459,14 +469,6 @@ class BackupManagerService extends IBackupManager.Stub {
addPackageParticipantsLockedInner(packageName, allApps);
}
- private void backUpPackageManagerData() {
- // No need to schedule a backup just for the metadata; just piggyback on
- // the next actual data backup.
- synchronized(this) {
- mDoPackageManager = true;
- }
- }
-
// The queue lock should be held when scheduling a backup pass
private void scheduleBackupPassLocked(long timeFromNowMillis) {
mBackupHandler.removeMessages(MSG_RUN_BACKUP);
@@ -564,6 +566,7 @@ class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "PerformBackupThread";
IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
+ File mStateDir;
File mJournal;
public PerformBackupThread(IBackupTransport transport, ArrayList<BackupRequest> queue,
@@ -571,32 +574,31 @@ class BackupManagerService extends IBackupManager.Stub {
mTransport = transport;
mQueue = queue;
mJournal = journal;
+
+ try {
+ mStateDir = new File(mBaseStateDir, transport.transportDirName());
+ } catch (RemoteException e) {
+ // can't happen; the transport is local
+ }
+ mStateDir.mkdirs();
}
@Override
public void run() {
if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
- // First, back up the package manager metadata if necessary
- boolean doPackageManager;
- synchronized (BackupManagerService.this) {
- doPackageManager = mDoPackageManager;
- mDoPackageManager = false;
- }
- if (doPackageManager) {
- // The package manager doesn't have a proper <application> etc, but since
- // it's running here in the system process we can just set up its agent
- // directly and use a synthetic BackupRequest.
- if (DEBUG) Log.i(TAG, "Running PM backup pass as well");
-
- PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
- mPackageManager, allAgentPackages());
- BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
- pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
- processOneBackup(pmRequest,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()),
- mTransport);
- }
+ // The package manager doesn't have a proper <application> etc, but since
+ // it's running here in the system process we can just set up its agent
+ // directly and use a synthetic BackupRequest. We always run this pass
+ // because it's cheap and this way we guarantee that we don't get out of
+ // step even if we're selecting among various transports at run time.
+ PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
+ mPackageManager, allAgentPackages());
+ BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
+ pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
+ processOneBackup(pmRequest,
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()),
+ mTransport);
// Now run all the backups in our queue
doQueuedBackups(mTransport);
@@ -758,8 +760,10 @@ class BackupManagerService extends IBackupManager.Stub {
class PerformRestoreThread extends Thread {
private IBackupTransport mTransport;
+ private IRestoreObserver mObserver;
private int mToken;
private RestoreSet mImage;
+ private File mStateDir;
class RestoreRequest {
public PackageInfo app;
@@ -771,9 +775,18 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- PerformRestoreThread(IBackupTransport transport, int restoreSetToken) {
+ PerformRestoreThread(IBackupTransport transport, IRestoreObserver observer,
+ int restoreSetToken) {
mTransport = transport;
+ mObserver = observer;
mToken = restoreSetToken;
+
+ try {
+ mStateDir = new File(mBaseStateDir, transport.transportDirName());
+ } catch (RemoteException e) {
+ // can't happen; the transport is local
+ }
+ mStateDir.mkdirs();
}
@Override
@@ -794,6 +807,8 @@ class BackupManagerService extends IBackupManager.Stub {
* 4. shut down the transport
*/
+ int error = -1; // assume error
+
// build the set of apps to restore
try {
RestoreSet[] images = mTransport.getAvailableRestoreSets();
@@ -820,6 +835,18 @@ class BackupManagerService extends IBackupManager.Stub {
List<PackageInfo> agentPackages = allAgentPackages();
restorePackages.addAll(agentPackages);
+ // let the observer know that we're running
+ if (mObserver != null) {
+ try {
+ // !!! TODO: get an actual count from the transport after
+ // its startRestore() runs?
+ mObserver.restoreStarting(restorePackages.size());
+ } catch (RemoteException e) {
+ Log.d(TAG, "Restore observer died at restoreStarting");
+ mObserver = null;
+ }
+ }
+
// STOPSHIP TODO: pick out the set for this token (instead of images[0])
long token = images[0].token;
if (!mTransport.startRestore(token, restorePackages.toArray(new PackageInfo[0]))) {
@@ -847,6 +874,7 @@ class BackupManagerService extends IBackupManager.Stub {
mPackageManager, agentPackages);
processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()));
+ int count = 0;
for (;;) {
packageName = mTransport.nextRestorePackage();
if (packageName == null) {
@@ -857,6 +885,16 @@ class BackupManagerService extends IBackupManager.Stub {
break;
}
+ if (mObserver != null) {
+ ++count;
+ try {
+ mObserver.onUpdate(count);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Restore observer died in onUpdate");
+ mObserver = null;
+ }
+ }
+
Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
if (metaInfo == null) {
Log.e(TAG, "Missing metadata for " + packageName);
@@ -900,6 +938,9 @@ class BackupManagerService extends IBackupManager.Stub {
mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
}
}
+
+ // if we get this far, report success to the observer
+ error = 0;
} catch (NameNotFoundException e) {
// STOPSHIP TODO: Handle the failure somehow?
Log.e(TAG, "Invalid paackage restoring data", e);
@@ -912,6 +953,14 @@ class BackupManagerService extends IBackupManager.Stub {
} catch (RemoteException e) {
Log.e(TAG, "Error finishing restore", e);
}
+
+ if (mObserver != null) {
+ try {
+ mObserver.restoreFinished(error);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Restore observer died at restoreFinished");
+ }
+ }
}
}
@@ -1149,14 +1198,15 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- public int performRestore(int token) throws android.os.RemoteException {
+ public int performRestore(int token, IRestoreObserver observer)
+ throws android.os.RemoteException {
mContext.enforceCallingPermission("android.permission.BACKUP", "performRestore");
if (mRestoreSets != null) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
- Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE,
- mRestoreTransport);
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(mRestoreTransport, observer);
msg.arg1 = token;
mBackupHandler.sendMessage(msg);
return 0;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index fd1dfc8..6d04b6b 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -10472,8 +10472,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// done with this agent
public void unbindBackupAgent(ApplicationInfo appInfo) {
if (DEBUG_BACKUP) Log.v(TAG, "unbindBackupAgent: " + appInfo);
+ if (appInfo == null) {
+ Log.w(TAG, "unbind backup agent for null app");
+ return;
+ }
synchronized(this) {
+ if (mBackupAppName == null) {
+ Log.w(TAG, "Unbinding backup agent with no active backup");
+ return;
+ }
+
if (!mBackupAppName.equals(appInfo.packageName)) {
Log.e(TAG, "Unbind of " + appInfo + " but is not the current backup target");
return;
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index 62a5d65..890ea63 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -78,6 +78,7 @@ public abstract class SMSDispatcher extends Handler {
protected static final String[] RAW_PROJECTION = new String[] {
"pdu",
"sequence",
+ "destination_port",
};
static final int MAIL_SEND_SMS = 1;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 2d43e0d..ecdc8f6 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -168,8 +168,8 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
int index = 0;
int msgType;
- int sourcePort;
- int destinationPort;
+ int sourcePort = 0;
+ int destinationPort = 0;
msgType = pdu[index++];
if (msgType != 0){
@@ -179,11 +179,14 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
totalSegments = pdu[index++]; // >=1
segment = pdu[index++]; // >=0
- //process WDP segment
- sourcePort = (0xFF & pdu[index++]) << 8;
- sourcePort |= 0xFF & pdu[index++];
- destinationPort = (0xFF & pdu[index++]) << 8;
- destinationPort |= 0xFF & pdu[index++];
+ // Only the first segment contains sourcePort and destination Port
+ if (segment == 0) {
+ //process WDP segment
+ sourcePort = (0xFF & pdu[index++]) << 8;
+ sourcePort |= 0xFF & pdu[index++];
+ destinationPort = (0xFF & pdu[index++]) << 8;
+ destinationPort |= 0xFF & pdu[index++];
+ }
// Lookup all other related parts
StringBuilder where = new StringBuilder("reference_number =");
@@ -224,6 +227,11 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
for (int i = 0; i < cursorCount; i++) {
cursor.moveToNext();
int cursorSequence = (int)cursor.getLong(sequenceColumn);
+ // Read the destination port from the first segment
+ if (cursorSequence == 0) {
+ int destinationPortColumn = cursor.getColumnIndex("destination_port");
+ destinationPort = (int)cursor.getLong(destinationPortColumn);
+ }
pdus[cursorSequence] = HexDump.hexStringToByteArray(
cursor.getString(pduColumn));
}
diff --git a/tests/backup/src/com/android/backuptest/BackupTestAgent.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java
index c6acc66..8e4fd39 100644
--- a/tests/backup/src/com/android/backuptest/BackupTestAgent.java
+++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java
@@ -18,11 +18,15 @@ package com.android.backuptest;
import android.backup.BackupHelperAgent;
import android.backup.FileBackupHelper;
+import android.backup.SharedPreferencesBackupHelper;
public class BackupTestAgent extends BackupHelperAgent
{
public void onCreate() {
addHelper("data_files", new FileBackupHelper(this, BackupTestActivity.FILE_NAME));
+ addHelper("more_data_files", new FileBackupHelper(this, "another_file.txt", "3.txt",
+ "empty.txt"));
+ addHelper("shared_prefs", new SharedPreferencesBackupHelper(this, "settings", "raw"));
}
}
diff --git a/tests/backup/test_backup.sh b/tests/backup/test_backup.sh
new file mode 100755
index 0000000..dbf9ed2
--- /dev/null
+++ b/tests/backup/test_backup.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+#adb kill-server
+
+# set the transport
+adb shell bmgr transport 1
+
+# load up the three files
+adb shell "rm /data/data/com.android.backuptest/files/* ; \
+ mkdir /data/data/com.android.backuptest ; \
+ mkdir /data/data/com.android.backuptest/files ; \
+ mkdir /data/data/com.android.backuptest/shared_prefs ; \
+ echo -n \"<map><int name=\\\"pref\\\" value=\\\"1\\\" /></map>\" > /data/data/com.android.backuptest/shared_prefs/raw.xml ; \
+ echo -n first file > /data/data/com.android.backuptest/files/file.txt ; \
+ echo -n asdf > /data/data/com.android.backuptest/files/another_file.txt ; \
+ echo -n 3 > /data/data/com.android.backuptest/files/3.txt ; \
+ echo -n "" > /data/data/com.android.backuptest/files/empty.txt ; \
+"
+
+# say that the data has changed
+adb shell bmgr backup com.android.backuptest
+
+# run the backup
+adb shell bmgr run
+
+
+
diff --git a/tests/backup/test_restore.sh b/tests/backup/test_restore.sh
new file mode 100755
index 0000000..ccf29cf
--- /dev/null
+++ b/tests/backup/test_restore.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+function check_file
+{
+ data=$(adb shell cat /data/data/com.android.backuptest/$1)
+ if [ "$data" = "$2" ] ; then
+ echo "$1 has correct value [$2]"
+ else
+ echo $1 is INCORRECT
+ echo " value: [$data]"
+ echo " expected: [$2]"
+ fi
+}
+
+# delete the old data
+echo --- Previous files
+adb shell "ls -l /data/data/com.android.backuptest/files"
+adb shell "rm /data/data/com.android.backuptest/files/*"
+echo --- Previous shared_prefs
+adb shell "ls -l /data/data/com.android.backuptest/shared_prefs"
+adb shell "rm /data/data/com.android.backuptest/shared_prefs/*"
+echo --- Erased files and shared_prefs
+adb shell "ls -l /data/data/com.android.backuptest/files"
+adb shell "ls -l /data/data/com.android.backuptest/shared_prefs"
+echo ---
+
+echo
+echo
+echo
+
+# run the restore
+adb shell bmgr restore 0
+
+echo
+echo
+echo
+
+# check the results
+check_file files/file.txt "first file"
+check_file files/another_file.txt "asdf"
+check_file files/3.txt "3"
+check_file files/empty.txt ""
+check_file shared_prefs/raw.xml '<map><int name="pref" value="1" /></map>'
+
+echo
+echo
+echo
+echo --- Restored files
+adb shell "ls -l /data/data/com.android.backuptest/files"
+echo --- Restored shared_prefs
+adb shell "ls -l /data/data/com.android.backuptest/shared_prefs"
+echo ---
+echo