diff options
27 files changed, 915 insertions, 105 deletions
@@ -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 |
