diff options
author | Christopher Tate <ctate@google.com> | 2009-06-14 21:13:03 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-06-14 21:13:03 -0700 |
commit | daf701fa6250ae89ad93e2e41127e0f676a322a5 (patch) | |
tree | 5b816f0e72d4bd2b0b2040242799c4008abf3a59 | |
parent | aa73f17201481f943345253328071118abc02933 (diff) | |
parent | 2fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2 (diff) | |
download | frameworks_base-daf701fa6250ae89ad93e2e41127e0f676a322a5.zip frameworks_base-daf701fa6250ae89ad93e2e41127e0f676a322a5.tar.gz frameworks_base-daf701fa6250ae89ad93e2e41127e0f676a322a5.tar.bz2 |
am 2fdd428e: Fix some backup reader/writer issues; make local transport do backup
Merge commit '2fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2'
* commit '2fdd428e0f18384160f7c38ce3a2cd9ba7e7b2c2':
Fix some backup reader/writer issues; make local transport do backup
Fix the jni initializer.
Add RestoreFileHelper, BackupDataInput, and add java wrappers for the methods on BackupDataOutput.
Fix bug #1812041: activity manager crash with bad args.
Journal backup requests so that they won't be lost in a crash
Fix data connection issues.
19 files changed, 655 insertions, 103 deletions
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java index 18d62e3..bf5cb5d 100644 --- a/core/java/android/app/FullBackupAgent.java +++ b/core/java/android/app/FullBackupAgent.java @@ -47,12 +47,11 @@ public class FullBackupAgent extends BackupAgent { } // That's the file set; now back it all up - FileBackupHelper.performBackup(this, oldState, data, newState, - (String[]) allFiles.toArray()); + FileBackupHelper helper = new FileBackupHelper(this); + helper.performBackup(oldState, data, newState, (String[])allFiles.toArray()); } @Override public void onRestore(ParcelFileDescriptor data, ParcelFileDescriptor newState) { } - } diff --git a/core/java/android/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java new file mode 100644 index 0000000..609dd90 --- /dev/null +++ b/core/java/android/backup/BackupDataInput.java @@ -0,0 +1,103 @@ +/* + * 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; + +import android.content.Context; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** @hide */ +public class BackupDataInput { + int mBackupReader; + + private EntityHeader mHeader = new EntityHeader(); + private boolean mHeaderReady; + + private static class EntityHeader { + String key; + int dataSize; + } + + public BackupDataInput(FileDescriptor fd) { + if (fd == null) throw new NullPointerException(); + mBackupReader = ctor(fd); + if (mBackupReader == 0) { + throw new RuntimeException("Native initialization failed with fd=" + fd); + } + } + + protected void finalize() throws Throwable { + try { + dtor(mBackupReader); + } finally { + super.finalize(); + } + } + + public boolean readNextHeader() throws IOException { + int result = readNextHeader_native(mBackupReader, mHeader); + if (result == 0) { + // read successfully + mHeaderReady = true; + return true; + } else if (result > 0) { + // done + mHeaderReady = false; + return false; + } else { + // error + mHeaderReady = false; + throw new IOException("result=0x" + Integer.toHexString(result)); + } + } + + public String getKey() { + if (mHeaderReady) { + return mHeader.key; + } else { + throw new IllegalStateException("mHeaderReady=false"); + } + } + + public int getDataSize() { + if (mHeaderReady) { + return mHeader.dataSize; + } else { + throw new IllegalStateException("mHeaderReady=false"); + } + } + + public int readEntityData(byte[] data, int size) throws IOException { + if (mHeaderReady) { + int result = readEntityData_native(mBackupReader, data, size); + if (result >= 0) { + return result; + } else { + throw new IOException("result=0x" + Integer.toHexString(result)); + } + } else { + throw new IllegalStateException("mHeaderReady=false"); + } + } + + private native static int ctor(FileDescriptor fd); + private native static void dtor(int mBackupReader); + + private native int readNextHeader_native(int mBackupReader, EntityHeader entity); + private native int readEntityData_native(int mBackupReader, byte[] data, int size); +} diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java index 25ae15b..1348d81 100644 --- a/core/java/android/backup/BackupDataOutput.java +++ b/core/java/android/backup/BackupDataOutput.java @@ -19,6 +19,7 @@ package android.backup; import android.content.Context; import java.io.FileDescriptor; +import java.io.IOException; /** @hide */ public class BackupDataOutput { @@ -37,6 +38,24 @@ public class BackupDataOutput { } } + public int writeEntityHeader(String key, int dataSize) throws IOException { + int result = writeEntityHeader_native(mBackupWriter, key, dataSize); + if (result >= 0) { + return result; + } else { + throw new IOException("result=0x" + Integer.toHexString(result)); + } + } + + public int writeEntityData(byte[] data, int size) throws IOException { + int result = writeEntityData_native(mBackupWriter, data, size); + if (result >= 0) { + return result; + } else { + throw new IOException("result=0x" + Integer.toHexString(result)); + } + } + protected void finalize() throws Throwable { try { dtor(mBackupWriter); @@ -47,5 +66,8 @@ public class BackupDataOutput { private native static int ctor(FileDescriptor fd); private native static void dtor(int mBackupWriter); + + private native static int writeEntityHeader_native(int mBackupWriter, String key, int dataSize); + private native static int writeEntityData_native(int mBackupWriter, byte[] data, int size); } diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java index 99051bf..ed840bb 100644 --- a/core/java/android/backup/FileBackupHelper.java +++ b/core/java/android/backup/FileBackupHelper.java @@ -27,21 +27,56 @@ import java.io.FileDescriptor; public class FileBackupHelper { private static final String TAG = "FileBackupHelper"; + Context mContext; + String mKeyPrefix; + + public FileBackupHelper(Context context) { + mContext = context; + } + + public FileBackupHelper(Context context, String keyPrefix) { + mContext = context; + mKeyPrefix = keyPrefix; + } + /** * Based on oldState, determine which of the files from the application's data directory * need to be backed up, write them to the data stream, and fill in newState with the * state as it exists now. */ - public static void performBackup(Context context, - ParcelFileDescriptor oldState, BackupDataOutput data, + public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState, String[] files) { - File base = context.getFilesDir(); + // file names + File base = mContext.getFilesDir(); final int N = files.length; String[] fullPaths = new String[N]; for (int i=0; i<N; i++) { fullPaths[i] = (new File(base, files[i])).getAbsolutePath(); } - performBackup_checked(oldState, data, newState, fullPaths, files); + + // keys + String[] keys = makeKeys(mKeyPrefix, files); + + // go + performBackup_checked(oldState, data, newState, fullPaths, keys); + } + + /** + * If keyPrefix is not null, prepend it to each of the strings in <code>original</code>; + * otherwise, return original. + */ + static String[] makeKeys(String keyPrefix, String[] original) { + if (keyPrefix != null) { + String[] keys; + final int N = original.length; + keys = new String[N]; + for (int i=0; i<N; i++) { + keys[i] = keyPrefix + ':' + original[i]; + } + return keys; + } else { + return original; + } } /** diff --git a/core/java/android/backup/RestoreHelper.java b/core/java/android/backup/RestoreHelper.java new file mode 100644 index 0000000..ebd9906 --- /dev/null +++ b/core/java/android/backup/RestoreHelper.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** @hide */ +public interface RestoreHelper { + public void performRestore(); +} + diff --git a/core/java/android/backup/RestoreHelperDistributor.java b/core/java/android/backup/RestoreHelperDistributor.java new file mode 100644 index 0000000..555ca79 --- /dev/null +++ b/core/java/android/backup/RestoreHelperDistributor.java @@ -0,0 +1,28 @@ +/* + * 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; + +import java.util.HashMap; + +/** @hide */ +public class RestoreHelperDistributor { + HashMap<String,RestoreHelper> mHelpers; + + public void addHelper(String keyPrefix, RestoreHelper helper) { + mHelpers.put(keyPrefix, helper); + } +} diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java index 923dc1b..cad79df 100644 --- a/core/java/android/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/backup/SharedPreferencesBackupHelper.java @@ -23,16 +23,33 @@ import java.io.FileDescriptor; /** @hide */ public class SharedPreferencesBackupHelper { - public static void performBackup(Context context, - ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot, + private Context mContext; + private String mKeyPrefix; + + public SharedPreferencesBackupHelper(Context context) { + mContext = context; + } + + public SharedPreferencesBackupHelper(Context context, String keyPrefix) { + mContext = context; + mKeyPrefix = keyPrefix; + } + + public void performBackup(ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot, BackupDataOutput data, String[] prefGroups) { + Context context = mContext; + // make filenames for the prefGroups final int N = prefGroups.length; String[] files = new String[N]; for (int i=0; i<N; i++) { - files[i] = context.getSharedPrefsFile(prefGroups[i]).toString(); + files[i] = context.getSharedPrefsFile(prefGroups[i]).getAbsolutePath(); } + // make keys if necessary + String[] keys = FileBackupHelper.makeKeys(mKeyPrefix, prefGroups); + + // go FileBackupHelper.performBackup_checked(oldSnapshot, data, newSnapshot, files, prefGroups); } } diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 62fba4a..83182f2 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -1,5 +1,6 @@ package com.android.internal.backup; +import android.backup.BackupDataInput; import android.backup.RestoreSet; import android.content.Context; import android.content.pm.PackageInfo; @@ -24,7 +25,7 @@ import java.util.ArrayList; public class LocalTransport extends IBackupTransport.Stub { private static final String TAG = "LocalTransport"; - private static final String DATA_FILE_NAME = "data"; + private static final boolean DEBUG = true; private Context mContext; private PackageManager mPackageManager; @@ -37,6 +38,7 @@ public class LocalTransport extends IBackupTransport.Stub { public LocalTransport(Context context) { + if (DEBUG) Log.v(TAG, "Transport constructed"); mContext = context; mPackageManager = context.getPackageManager(); } @@ -47,29 +49,63 @@ public class LocalTransport extends IBackupTransport.Stub { } public int startSession() throws RemoteException { + if (DEBUG) Log.v(TAG, "session started"); + mDataDir.mkdirs(); return 0; } public int endSession() throws RemoteException { + if (DEBUG) Log.v(TAG, "session ended"); return 0; } public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) throws RemoteException { - File packageDir = new File(mDataDir, packageInfo.packageName); - File imageFileName = new File(packageDir, DATA_FILE_NAME); - - //!!! TODO: process the (partial) update into the persistent restore set: - - // Parse out the existing image file into the key/value map + if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName); + int err = 0; - // Parse out the backup data into the key/value updates - - // Apply the backup key/value updates to the image + File packageDir = new File(mDataDir, packageInfo.packageName); + packageDir.mkdirs(); - // Write out the image in the canonical format + // Each 'record' in the restore set is kept in its own file, named by + // the record key. Wind through the data file, extracting individual + // record operations and building a set of all the updates to apply + // in this update. + BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor()); + try { + int bufSize = 512; + byte[] buf = new byte[bufSize]; + while (changeSet.readNextHeader()) { + String key = changeSet.getKey(); + int dataSize = changeSet.getDataSize(); + if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize); + if (dataSize > bufSize) { + bufSize = dataSize; + buf = new byte[bufSize]; + } + changeSet.readEntityData(buf, dataSize); + if (DEBUG) Log.v(TAG, " + data size " + dataSize); + + File entityFile = new File(packageDir, key); + FileOutputStream entity = new FileOutputStream(entityFile); + try { + entity.write(buf, 0, dataSize); + } catch (IOException e) { + Log.e(TAG, "Unable to update key file " + + entityFile.getAbsolutePath()); + err = -1; + } finally { + entity.close(); + } + } + } catch (IOException e) { + // oops, something went wrong. abort the operation and return error. + Log.v(TAG, "Exception reading backup input:"); + e.printStackTrace(); + err = -1; + } - return -1; + return err; } // Restore handling @@ -83,6 +119,7 @@ public class LocalTransport extends IBackupTransport.Stub { } public PackageInfo[] getAppSet(int token) throws android.os.RemoteException { + if (DEBUG) Log.v(TAG, "getting app set " + token); // the available packages are the extant subdirs of mDatadir File[] packageDirs = mDataDir.listFiles(mDirFileFilter); ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>(); @@ -99,9 +136,11 @@ public class LocalTransport extends IBackupTransport.Stub { } } - Log.v(TAG, "Built app set of " + packages.size() + " entries:"); - for (PackageInfo p : packages) { - Log.v(TAG, " + " + p.packageName); + if (DEBUG) { + Log.v(TAG, "Built app set of " + packages.size() + " entries:"); + for (PackageInfo p : packages) { + Log.v(TAG, " + " + p.packageName); + } } PackageInfo[] result = new PackageInfo[packages.size()]; @@ -110,16 +149,25 @@ public class LocalTransport extends IBackupTransport.Stub { public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor output) throws android.os.RemoteException { + if (DEBUG) Log.v(TAG, "getting restore data " + token + " : " + packageInfo.packageName); // we only support one hardcoded restore set if (token != 0) return -1; // the data for a given package is at a known location File packageDir = new File(mDataDir, packageInfo.packageName); - File imageFile = new File(packageDir, DATA_FILE_NAME); - // restore is relatively easy: we already maintain the full data set in - // the canonical form understandable to the BackupAgent - return copyFileToFD(imageFile, output); + // The restore set is the concatenation of the individual record blobs, + // each of which is a file in the package's directory + File[] blobs = packageDir.listFiles(); + int err = 0; + if (blobs != null && blobs.length > 0) { + for (File f : blobs) { + err = copyFileToFD(f, output); + if (err != 0) break; + } + } + + return err; } private int copyFileToFD(File source, ParcelFileDescriptor dest) { diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 31cc4f7..aca6670 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -115,6 +115,7 @@ LOCAL_SRC_FILES:= \ android_location_GpsLocationProvider.cpp \ com_android_internal_os_ZygoteInit.cpp \ com_android_internal_graphics_NativeUtils.cpp \ + android_backup_BackupDataInput.cpp \ android_backup_BackupDataOutput.cpp \ android_backup_FileBackupHelper.cpp diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6de37f0..4512fef 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -153,6 +153,7 @@ extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env); extern int register_android_util_Base64(JNIEnv* env); 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_FileBackupHelper(JNIEnv *env); @@ -1168,6 +1169,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_ddm_DdmHandleNativeHeap), REG_JNI(register_android_util_Base64), REG_JNI(register_android_location_GpsLocationProvider), + REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), REG_JNI(register_android_backup_FileBackupHelper), }; diff --git a/core/jni/android_backup_BackupDataInput.cpp b/core/jni/android_backup_BackupDataInput.cpp new file mode 100644 index 0000000..5b2fb73 --- /dev/null +++ b/core/jni/android_backup_BackupDataInput.cpp @@ -0,0 +1,173 @@ +/* + * 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 "FileBackupHelper_native" +#include <utils/Log.h> + +#include "JNIHelp.h" +#include <android_runtime/AndroidRuntime.h> + +#include <utils/BackupHelpers.h> + +namespace android +{ + +// java.io.FileDescriptor +static jfieldID s_descriptorField = 0; + +// android.backup.BackupDataInput$EntityHeader +static jfieldID s_keyField = 0; +static jfieldID s_dataSizeField = 0; + +static int +ctor_native(JNIEnv* env, jobject clazz, jobject fileDescriptor) +{ + int err; + + int fd = env->GetIntField(fileDescriptor, s_descriptorField); + if (fd == -1) { + return NULL; + } + + return (int)new BackupDataReader(fd); +} + +static void +dtor_native(JNIEnv* env, jobject clazz, int r) +{ + delete (BackupDataReader*)r; +} + +static jint +readNextHeader_native(JNIEnv* env, jobject clazz, int r, jobject entity) +{ + int err; + BackupDataReader* reader = (BackupDataReader*)r; + + err = reader->Status(); + if (err != 0) { + return err < 0 ? err : -1; + } + + int type = 0; + + err = reader->ReadNextHeader(&type); + if (err == EIO) { + // Clean EOF with no footer block; just claim we're done + return 1; + } + + if (err != 0) { + return err < 0 ? err : -1; + } + + switch (type) { + case BACKUP_HEADER_APP_V1: + { + String8 packageName; + int cookie; + err = reader->ReadAppHeader(&packageName, &cookie); + if (err != 0) { + LOGD("ReadAppHeader() returned %d; aborting", err); + return err < 0 ? err : -1; + } + break; + } + case BACKUP_HEADER_ENTITY_V1: + { + String8 key; + size_t dataSize; + err = reader->ReadEntityHeader(&key, &dataSize); + if (err != 0) { + LOGD("ReadEntityHeader(); aborting", err); + return err < 0 ? err : -1; + } + // TODO: Set the fields in the entity object + jstring keyStr = env->NewStringUTF(key.string()); + env->SetObjectField(entity, s_keyField, keyStr); + env->SetIntField(entity, s_dataSizeField, dataSize); + return 0; + } + case BACKUP_FOOTER_APP_V1: + { + break; + } + default: + LOGD("Unknown header type: 0x%08x\n", type); + return -1; + } + + // done + return 1; +} + +static jint +readEntityData_native(JNIEnv* env, jobject clazz, int r, jbyteArray data, int size) +{ + int err; + BackupDataReader* reader = (BackupDataReader*)r; + + if (env->GetArrayLength(data) < size) { + // size mismatch + return -1; + } + + jbyte* dataBytes = env->GetByteArrayElements(data, NULL); + if (dataBytes == NULL) { + return -2; + } + + err = reader->ReadEntityData(dataBytes, size); + + env->ReleaseByteArrayElements(data, dataBytes, 0); + + return err; +} + +static const JNINativeMethod g_methods[] = { + { "ctor", "(Ljava/io/FileDescriptor;)I", (void*)ctor_native }, + { "dtor", "(I)V", (void*)dtor_native }, + { "readNextHeader_native", "(ILandroid/backup/BackupDataInput$EntityHeader;)I", + (void*)readNextHeader_native }, + { "readEntityData_native", "(I[BI)I", (void*)readEntityData_native }, +}; + +int register_android_backup_BackupDataInput(JNIEnv* env) +{ + //LOGD("register_android_backup_BackupDataInput"); + + 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/BackupDataInput$EntityHeader"); + LOG_FATAL_IF(clazz == NULL, "Unable to find class android.backup.BackupDataInput.EntityHeader"); + s_keyField = env->GetFieldID(clazz, "key", "Ljava/lang/String;"); + LOG_FATAL_IF(s_keyField == NULL, + "Unable to find key field in android.backup.BackupDataInput.EntityHeader"); + s_dataSizeField = env->GetFieldID(clazz, "dataSize", "I"); + LOG_FATAL_IF(s_dataSizeField == NULL, + "Unable to find dataSize field in android.backup.BackupDataInput.EntityHeader"); + + return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupDataInput", + g_methods, NELEM(g_methods)); +} + +} diff --git a/core/jni/android_backup_BackupDataOutput.cpp b/core/jni/android_backup_BackupDataOutput.cpp index aab1233..6362439 100644 --- a/core/jni/android_backup_BackupDataOutput.cpp +++ b/core/jni/android_backup_BackupDataOutput.cpp @@ -28,7 +28,7 @@ namespace android static jfieldID s_descriptorField = 0; static int -ctor_native(JNIEnv* env, jobject This, jobject fileDescriptor) +ctor_native(JNIEnv* env, jobject clazz, jobject fileDescriptor) { int err; @@ -41,19 +41,62 @@ ctor_native(JNIEnv* env, jobject This, jobject fileDescriptor) } static void -dtor_native(JNIEnv* env, jobject This, int fd) +dtor_native(JNIEnv* env, jobject clazz, int w) { - delete (BackupDataWriter*)fd; + delete (BackupDataWriter*)w; +} + +static jint +writeEntityHeader_native(JNIEnv* env, jobject clazz, int w, jstring key, int dataSize) +{ + int err; + BackupDataWriter* writer = (BackupDataWriter*)w; + + const char* keyUTF = env->GetStringUTFChars(key, NULL); + if (keyUTF == NULL) { + return -1; + } + + err = writer->WriteEntityHeader(String8(keyUTF), dataSize); + + env->ReleaseStringUTFChars(key, keyUTF); + + return err; +} + +static jint +writeEntityData_native(JNIEnv* env, jobject clazz, int w, jbyteArray data, int size) +{ + int err; + BackupDataWriter* writer = (BackupDataWriter*)w; + + if (env->GetArrayLength(data) > size) { + // size mismatch + return -1; + } + + jbyte* dataBytes = env->GetByteArrayElements(data, NULL); + if (dataBytes == NULL) { + return -1; + } + + err = writer->WriteEntityData(dataBytes, size); + + env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT); + + return err; } static const JNINativeMethod g_methods[] = { { "ctor", "(Ljava/io/FileDescriptor;)I", (void*)ctor_native }, { "dtor", "(I)V", (void*)dtor_native }, + { "writeEntityHeader_native", "(ILjava/lang/String;I)I", (void*)writeEntityHeader_native }, + { "writeEntityData_native", "(I[BI)I", (void*)writeEntityData_native }, }; int register_android_backup_BackupDataOutput(JNIEnv* env) { - LOGD("register_android_backup_BackupDataOutput"); + //LOGD("register_android_backup_BackupDataOutput"); jclass clazz; diff --git a/core/jni/android_backup_FileBackupHelper.cpp b/core/jni/android_backup_FileBackupHelper.cpp index 2ee064b..418db8a 100644 --- a/core/jni/android_backup_FileBackupHelper.cpp +++ b/core/jni/android_backup_FileBackupHelper.cpp @@ -25,6 +25,7 @@ namespace android { +// java.io.FileDescriptor static jfieldID s_descriptorField = 0; static int @@ -34,7 +35,6 @@ performBackup_native(JNIEnv* env, jobject clazz, jobject oldState, int data, int err; // all parameters have already been checked against null - LOGD("oldState=%p newState=%p data=%p\n", oldState, newState, data); int oldStateFD = oldState != NULL ? env->GetIntField(oldState, s_descriptorField) : -1; int newStateFD = env->GetIntField(newState, s_descriptorField); BackupDataWriter* dataStream = (BackupDataWriter*)data; @@ -74,8 +74,6 @@ static const JNINativeMethod g_methods[] = { int register_android_backup_FileBackupHelper(JNIEnv* env) { - LOGD("register_android_backup_FileBackupHelper"); - jclass clazz; clazz = env->FindClass("java/io/FileDescriptor"); diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp index 120f23d..8c9f875 100644 --- a/libs/utils/BackupData.cpp +++ b/libs/utils/BackupData.cpp @@ -327,7 +327,7 @@ BackupDataReader::ReadAppHeader(String8* packageName, int* cookie) } size_t size = m_header.app.packageLen; char* buf = packageName->lockBuffer(size); - if (packageName == NULL) { + if (buf == NULL) { packageName->unlockBuffer(); m_status = ENOMEM; return m_status; diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index d3067ec..47a6ede 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -51,11 +51,13 @@ import com.android.internal.backup.LocalTransport; import com.android.internal.backup.GoogleTransport; import com.android.internal.backup.IBackupTransport; +import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; +import java.io.RandomAccessFile; import java.lang.String; import java.util.ArrayList; import java.util.HashMap; @@ -122,6 +124,9 @@ class BackupManagerService extends IBackupManager.Stub { private File mStateDir; private File mDataDir; + private File mJournalDir; + private File mJournal; + private RandomAccessFile mJournalStream; public BackupManagerService(Context context) { mContext = context; @@ -133,6 +138,11 @@ class BackupManagerService extends IBackupManager.Stub { mStateDir.mkdirs(); mDataDir = Environment.getDownloadCacheDirectory(); + // Set up the backup-request journaling + mJournalDir = new File(mStateDir, "pending"); + mJournalDir.mkdirs(); + makeJournalLocked(); // okay because no other threads are running yet + //!!! TODO: default to cloud transport, not local mTransportId = BackupManager.TRANSPORT_LOCAL; @@ -141,6 +151,10 @@ class BackupManagerService extends IBackupManager.Stub { addPackageParticipantsLocked(null); } + // Now that we know about valid backup participants, parse any + // leftover journal files and schedule a new backup pass + parseLeftoverJournals(); + // Register for broadcasts about package install, etc., so we can // update the provider list. IntentFilter filter = new IntentFilter(); @@ -150,6 +164,46 @@ class BackupManagerService extends IBackupManager.Stub { mContext.registerReceiver(mBroadcastReceiver, filter); } + private void makeJournalLocked() { + try { + mJournal = File.createTempFile("journal", null, mJournalDir); + mJournalStream = new RandomAccessFile(mJournal, "rwd"); + } catch (IOException e) { + Log.e(TAG, "Unable to write backup journals"); + mJournal = null; + mJournalStream = null; + } + } + + private void parseLeftoverJournals() { + if (mJournal != null) { + File[] allJournals = mJournalDir.listFiles(); + for (File f : allJournals) { + if (f.compareTo(mJournal) != 0) { + // This isn't the current journal, so it must be a leftover. Read + // out the package names mentioned there and schedule them for + // backup. + try { + Log.i(TAG, "Found stale backup journal, scheduling:"); + RandomAccessFile in = new RandomAccessFile(f, "r"); + while (true) { + String packageName = in.readUTF(); + Log.i(TAG, " + " + packageName); + dataChanged(packageName); + } + } catch (EOFException e) { + // no more data; we're done + } catch (Exception e) { + // can't read it or other error; just skip it + } finally { + // close/delete the file + f.delete(); + } + } + } + } + } + // ----- Track installation/removal of packages ----- BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { @@ -198,6 +252,8 @@ class BackupManagerService extends IBackupManager.Stub { switch (msg.what) { case MSG_RUN_BACKUP: // snapshot the pending-backup set and work on that + File oldJournal = mJournal; + RandomAccessFile oldJournalStream = mJournalStream; synchronized (mQueueLock) { if (mBackupQueue == null) { mBackupQueue = new ArrayList<BackupRequest>(); @@ -207,10 +263,22 @@ class BackupManagerService extends IBackupManager.Stub { mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>(); } // !!! TODO: start a new backup-queue journal file too - // WARNING: If we crash after this line, anything in mPendingBackups will - // be lost. FIX THIS. + if (mJournalStream != null) { + try { + mJournalStream.close(); + } catch (IOException e) { + // don't need to do anything + } + makeJournalLocked(); + } + + // At this point, we have started a new journal file, and the old + // file identity is being passed to the backup processing thread. + // When it completes successfully, that old journal file will be + // deleted. If we crash prior to that, the old journal is parsed + // at next boot and the journaled requests fulfilled. } - (new PerformBackupThread(mTransportId, mBackupQueue)).run(); + (new PerformBackupThread(mTransportId, mBackupQueue, oldJournal)).run(); break; case MSG_RUN_FULL_BACKUP: @@ -433,10 +501,13 @@ class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "PerformBackupThread"; int mTransport; ArrayList<BackupRequest> mQueue; + File mJournal; - public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue) { + public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue, + File journal) { mTransport = transportId; mQueue = queue; + mJournal = journal; } @Override @@ -468,6 +539,10 @@ class BackupManagerService extends IBackupManager.Stub { Log.e(TAG, "Error ending transport"); e.printStackTrace(); } + + if (!mJournal.delete()) { + Log.e(TAG, "Unable to remove backup journal file " + mJournal.getAbsolutePath()); + } } private void doQueuedBackups(IBackupTransport transport) { @@ -782,7 +857,9 @@ class BackupManagerService extends IBackupManager.Stub { // one already there, then overwrite it, but no harm done. BackupRequest req = new BackupRequest(app, false); mPendingBackups.put(app, req); - // !!! TODO: write to the pending-backup journal file in case of crash + + // Journal this request in case of crash + writeToJournalLocked(packageName); } } @@ -804,6 +881,18 @@ class BackupManagerService extends IBackupManager.Stub { } } + private void writeToJournalLocked(String str) { + if (mJournalStream != null) { + try { + mJournalStream.writeUTF(str); + } catch (IOException e) { + Log.e(TAG, "Error writing to backup journal"); + mJournalStream = null; + mJournal = null; + } + } + } + // Schedule a backup pass for a given package. This method will schedule a // full backup even for apps that do not declare an android:backupAgent, so // use with care. diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 6a81178..07d6b6f 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -17,7 +17,6 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; -import com.android.internal.os.RuntimeInit; import com.android.server.IntentResolver; import com.android.server.ProcessMap; import com.android.server.ProcessStats; @@ -2761,7 +2760,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // instance of the activity so a new fresh one can be started. if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { if (!ret.finishing) { - int index = indexOfTokenLocked(ret, false); + int index = indexOfTokenLocked(ret); if (index >= 0) { finishActivityLocked(ret, 0, Activity.RESULT_CANCELED, null, "clear"); @@ -2854,7 +2853,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen HistoryRecord sourceRecord = null; HistoryRecord resultRecord = null; if (resultTo != null) { - int index = indexOfTokenLocked(resultTo, false); + int index = indexOfTokenLocked(resultTo); if (DEBUG_RESULTS) Log.v( TAG, "Sending result to " + resultTo + " (index " + index + ")"); if (index >= 0) { @@ -3420,7 +3419,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized (this) { - int index = indexOfTokenLocked(callingActivity, false); + int index = indexOfTokenLocked(callingActivity); if (index < 0) { return false; } @@ -3570,7 +3569,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void setRequestedOrientation(IBinder token, int requestedOrientation) { synchronized (this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index < 0) { return; } @@ -3592,7 +3591,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public int getRequestedOrientation(IBinder token) { synchronized (this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index < 0) { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } @@ -3648,7 +3647,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen TAG, "Finishing activity: token=" + token + ", result=" + resultCode + ", data=" + resultData); - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index < 0) { return false; } @@ -3772,7 +3771,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final HistoryRecord finishCurrentActivityLocked(HistoryRecord r, int mode) { - final int index = indexOfTokenLocked(r, false); + final int index = indexOfTokenLocked(r); if (index < 0) { return null; } @@ -3897,7 +3896,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public final void finishSubActivity(IBinder token, String resultWho, int requestCode) { synchronized(this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index < 0) { return; } @@ -4447,7 +4446,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized(this) { - int index = indexOfTokenLocked(token, true); + int index = indexOfTokenLocked(token); if (index < 0) { return; } @@ -5012,7 +5011,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Get the activity record. - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); @@ -5166,7 +5165,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen HistoryRecord r = null; synchronized (this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index >= 0) { r = (HistoryRecord)mHistory.get(index); if (!timeout) { @@ -5197,7 +5196,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); synchronized (this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index >= 0) { r = (HistoryRecord)mHistory.get(index); r.thumbnail = thumbnail; @@ -5227,7 +5226,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { mHandler.removeMessages(DESTROY_TIMEOUT_MSG, token); - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); if (r.state == ActivityState.DESTROYING) { @@ -5254,7 +5253,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } private HistoryRecord getCallingRecordLocked(IBinder token) { - int index = indexOfTokenLocked(token, true); + int index = indexOfTokenLocked(token); if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); if (r != null) { @@ -5266,7 +5265,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public ComponentName getActivityClassForToken(IBinder token) { synchronized(this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); return r.intent.getComponent(); @@ -5277,7 +5276,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public String getPackageForToken(IBinder token) { synchronized(this) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); return r.packageName; @@ -5316,7 +5315,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } HistoryRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index < 0) { return null; } @@ -6837,7 +6836,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (r == null) { - int index = indexOfTokenLocked(token, false); + int index = indexOfTokenLocked(token); if (index < 0) { return; } @@ -8943,7 +8942,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return false; } - private final int indexOfTokenLocked(IBinder token, boolean required) { + private final int indexOfTokenLocked(IBinder token) { int count = mHistory.size(); // convert the token to an entry in the history. @@ -8957,19 +8956,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen break; } } - if (index < 0 && required) { - RuntimeInit.crash(TAG, new InvalidTokenException(token)); - } return index; } - static class InvalidTokenException extends Exception { - InvalidTokenException(IBinder token) { - super("Bad activity token: " + token); - } - } - private final void killServicesLocked(ProcessRecord app, boolean allowRestart) { // Report disconnected services. @@ -9994,7 +9984,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen HistoryRecord activity = null; if (token != null) { - int aindex = indexOfTokenLocked(token, false); + int aindex = indexOfTokenLocked(token); if (aindex < 0) { Log.w(TAG, "Binding with unknown activity: " + token); return 0; diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java index 42f8fac..c922cec 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java @@ -404,6 +404,8 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { mReconnectIntent = null; } + setState(State.DISCONNECTING); + for (DataConnection connBase : dataConnectionList) { CdmaDataConnection conn = (CdmaDataConnection) connBase; @@ -419,24 +421,9 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { stopNetStatPoll(); - /* - * If we've been asked to tear down the connection, - * set the state to DISCONNECTING. However, there's - * a race that can occur if for some reason we were - * already in the IDLE state. In that case, the call - * to conn.disconnect() above will immediately post - * a message to the handler thread that the disconnect - * is done, and if the handler runs before the code - * below does, the handler will have set the state to - * IDLE before the code below runs. If we didn't check - * for that, future calls to trySetupData would fail, - * and we would never get out of the DISCONNECTING state. - */ if (!tearDown) { setState(State.IDLE); phone.notifyDataConnection(reason); - } else if (state != State.IDLE) { - setState(State.DISCONNECTING); } } @@ -811,6 +798,8 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { resetPollStats(); } } else { + // reset reconnect timer + nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; // in case data setup was attempted when we were on a voice call trySetupData(Phone.REASON_VOICE_CALL_ENDED); } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 270f78b..71af406 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -636,6 +636,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { mReconnectIntent = null; } + setState(State.DISCONNECTING); + for (DataConnection conn : pdpList) { PdpConnection pdp = (PdpConnection) conn; if (tearDown) { @@ -647,25 +649,10 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } stopNetStatPoll(); - /* - * If we've been asked to tear down the connection, - * set the state to DISCONNECTING. However, there's - * a race that can occur if for some reason we were - * already in the IDLE state. In that case, the call - * to pdp.disconnect() above will immediately post - * a message to the handler thread that the disconnect - * is done, and if the handler runs before the code - * below does, the handler will have set the state to - * IDLE before the code below runs. If we didn't check - * for that, future calls to trySetupData would fail, - * and we would never get out of the DISCONNECTING state. - */ if (!tearDown) { setState(State.IDLE); phone.notifyDataConnection(reason); mActiveApn = null; - } else if (state != State.IDLE) { - setState(State.DISCONNECTING); } } @@ -813,6 +800,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if (state != State.DISCONNECTING) { cleanUpConnection(isConnected, Phone.REASON_APN_CHANGED); if (!isConnected) { + // reset reconnect timer + nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; trySetupData(Phone.REASON_APN_CHANGED); } } @@ -1406,6 +1395,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { resetPollStats(); } } else { + // reset reconnect timer + nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; // in case data setup was attempted when we were on a voice call trySetupData(Phone.REASON_VOICE_CALL_ENDED); } diff --git a/tests/backup/src/com/android/backuptest/BackupTestAgent.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java index 11e520e..a370d69 100644 --- a/tests/backup/src/com/android/backuptest/BackupTestAgent.java +++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java @@ -30,7 +30,8 @@ public class BackupTestAgent extends BackupAgent public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { Log.d(TAG, "onBackup"); - FileBackupHelper.performBackup(this, oldState, data, newState, new String[] { + FileBackupHelper helper = new FileBackupHelper(this); + helper.performBackup(oldState, data, newState, new String[] { BackupTestActivity.FILE_NAME }); } |