Merge "Cleaned up the old test cases and test media files."
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 17f8adb..63f3258 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -278,7 +278,6 @@
                 int token, IBackupManager callbackBinder) throws RemoteException {
             long ident = Binder.clearCallingIdentity();
             try {
-Log.d(TAG, "doRestoreFile() => onRestoreFile()");
                 BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
             } catch (IOException e) {
                 throw new RuntimeException(e);
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index dfb0dd7..3b70e19 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -46,7 +46,7 @@
     public static final String SHARED_STORAGE_TOKEN = "shared";
 
     public static final String APPS_PREFIX = "apps/";
-    public static final String SHARED_PREFIX = "shared/";
+    public static final String SHARED_PREFIX = SHARED_STORAGE_TOKEN + "/";
 
     public static final String FULL_BACKUP_INTENT_ACTION = "fullback";
     public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
@@ -61,7 +61,8 @@
             String linkdomain, String rootpath, String path, BackupDataOutput output);
 
     static public void restoreToFile(ParcelFileDescriptor data,
-            long size, int type, long mode, long mtime, File outFile) throws IOException {
+            long size, int type, long mode, long mtime, File outFile,
+            boolean doChmod) throws IOException {
         if (type == FullBackup.TYPE_DIRECTORY) {
             // Canonically a directory has no associated content, so we don't need to read
             // anything from the pipe in this case.  Just create the directory here and
@@ -116,7 +117,7 @@
         }
 
         // Now twiddle the state to match the backup, assuming all went well
-        if (outFile != null) {
+        if (doChmod && outFile != null) {
             try {
                 Libcore.os.chmod(outFile.getPath(), (int)mode);
             } catch (ErrnoException e) {
diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java
index 4dca593..df1c363 100644
--- a/core/java/android/app/backup/FullBackupAgent.java
+++ b/core/java/android/app/backup/FullBackupAgent.java
@@ -28,8 +28,6 @@
 import libcore.io.StructStat;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -84,9 +82,10 @@
 
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-            ParcelFileDescriptor newState) {
+            ParcelFileDescriptor newState) throws IOException {
         // Filters, the scan queue, and the set of resulting entities
         HashSet<String> filterSet = new HashSet<String>();
+        String packageName = getPackageName();
 
         // Okay, start with the app's root tree, but exclude all of the canonical subdirs
         if (mLibDir != null) {
@@ -96,25 +95,28 @@
         filterSet.add(mDatabaseDir);
         filterSet.add(mSharedPrefsDir);
         filterSet.add(mFilesDir);
-        processTree(FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);
+        processTree(packageName, FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);
 
         // Now do the same for the files dir, db dir, and shared prefs dir
         filterSet.add(mMainDir);
         filterSet.remove(mFilesDir);
-        processTree(FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);
+        processTree(packageName, FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);
 
         filterSet.add(mFilesDir);
         filterSet.remove(mDatabaseDir);
-        processTree(FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);
+        processTree(packageName, FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);
 
         filterSet.add(mDatabaseDir);
         filterSet.remove(mSharedPrefsDir);
-        processTree(FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
+        processTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
     }
 
-    private void processTree(String domain, String rootPath,
+    // Scan the dir tree (if it actually exists) and process each entry we find.  If the
+    // 'excludes' parameter is non-null, it is consulted each time a new file system entity
+    // is visited to see whether that entity (and its subtree, if appropriate) should be
+    // omitted from the backup process.
+    protected void processTree(String packageName, String domain, String rootPath,
             HashSet<String> excludes, BackupDataOutput data) {
-        // Scan the dir tree (if it actually exists) and process each entry we find
         File rootFile = new File(rootPath);
         if (rootFile.exists()) {
             LinkedList<File> scanQueue = new LinkedList<File>();
@@ -125,7 +127,7 @@
                 String filePath = file.getAbsolutePath();
 
                 // prune this subtree?
-                if (excludes.contains(filePath)) {
+                if (excludes != null && excludes.contains(filePath)) {
                     continue;
                 }
 
@@ -149,7 +151,7 @@
                 }
 
                 // Finally, back this file up before proceeding
-                FullBackup.backupToTar(getPackageName(), domain, null, rootPath, filePath, data);
+                FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, data);
             }
         }
     }
@@ -218,6 +220,6 @@
         if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath());
 
         // Now that we've figured out where the data goes, send it on its way
-        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
+        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true);
     }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 09fede0..31ad6e9 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1517,11 +1517,12 @@
             }
         }
 
+        // fullBackupAgent is explicitly handled even if allowBackup is false
         name = sa.getNonConfigurationString(
                 com.android.internal.R.styleable.AndroidManifestApplication_fullBackupAgent, 0);
         if (name != null) {
             ai.fullBackupAgentName = buildClassName(pkgName, name, outError);
-            if (true) {
+            if (false) {
                 Log.v(TAG, "android:fullBackupAgent=" + ai.fullBackupAgentName
                         + " from " + pkgName + "+" + name);
             }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index bc4208a..792e4c1 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -32,6 +32,7 @@
     private final boolean mRemovable;
     private final boolean mEmulated;
     private final int mMtpReserveSpace;
+    private final boolean mAllowMassStorage;
     private int mStorageId;
 
     // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
@@ -39,23 +40,25 @@
     // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
     public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
 
-    public StorageVolume(String path, String description,
-            boolean removable, boolean emulated, int mtpReserveSpace) {
+    public StorageVolume(String path, String description, boolean removable,
+            boolean emulated, int mtpReserveSpace, boolean allowMassStorage) {
         mPath = path;
         mDescription = description;
         mRemovable = removable;
         mEmulated = emulated;
         mMtpReserveSpace = mtpReserveSpace;
+        mAllowMassStorage = allowMassStorage;
     }
 
     // for parcelling only
-    private StorageVolume(String path, String description,
-            boolean removable, boolean emulated, int mtpReserveSpace, int storageId) {
+    private StorageVolume(String path, String description, boolean removable,
+            boolean emulated, int mtpReserveSpace, int storageId, boolean allowMassStorage) {
         mPath = path;
         mDescription = description;
         mRemovable = removable;
         mEmulated = emulated;
         mMtpReserveSpace = mtpReserveSpace;
+        mAllowMassStorage = allowMassStorage;
         mStorageId = storageId;
     }
 
@@ -130,6 +133,15 @@
         return mMtpReserveSpace;
     }
 
+    /**
+     * Returns true if this volume can be shared via USB mass storage.
+     *
+     * @return whether mass storage is allowed
+     */
+    public boolean allowMassStorage() {
+        return mAllowMassStorage;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof StorageVolume && mPath != null) {
@@ -158,9 +170,10 @@
             int emulated = in.readInt();
             int storageId = in.readInt();
             int mtpReserveSpace = in.readInt();
+            int allowMassStorage = in.readInt();
             return new StorageVolume(path, description,
                     removable == 1, emulated == 1,
-                    mtpReserveSpace, storageId);
+                    mtpReserveSpace, storageId, allowMassStorage == 1);
         }
 
         public StorageVolume[] newArray(int size) {
@@ -179,5 +192,6 @@
         parcel.writeInt(mEmulated ? 1 : 0);
         parcel.writeInt(mStorageId);
         parcel.writeInt(mMtpReserveSpace);
+        parcel.writeInt(mAllowMassStorage ? 1 : 0);
     }
 }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 2df2688..a06d0f6 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -183,6 +183,15 @@
          */
         public static final String DIRTY = "dirty";
 
+        /**
+         * If set to 1 this causes events on this calendar to be duplicated with
+         * {@link EventsColumns#LAST_SYNCED} set to 1 whenever the event transitions from non-dirty
+         * to dirty. The duplicated event will not be expanded in the instances table and will only
+         * show up in sync adapter queries of the events table. It will also be deleted when the
+         * originating event has its dirty flag cleared by the sync adapter.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String CAN_PARTIALLY_UPDATE = "canPartiallyUpdate";
     }
 
     /**
@@ -317,7 +326,8 @@
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
                         Calendars.DISPLAY_NAME);
-                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, Calendars.CALENDAR_COLOR);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+                        Calendars.CALENDAR_COLOR);
                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
@@ -332,6 +342,8 @@
                         Calendars.CAN_MODIFY_TIME_ZONE);
                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
                         Calendars.MAX_REMINDERS);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+                        Calendars.CAN_PARTIALLY_UPDATE);
 
                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
 
@@ -512,6 +524,7 @@
             MAX_REMINDERS,
             CAN_MODIFY_TIME_ZONE,
             CAN_ORGANIZER_RESPOND,
+            CAN_PARTIALLY_UPDATE,
             CALENDAR_LOCATION,
             CALENDAR_TIMEZONE,
             ACCESS_LEVEL,
@@ -686,6 +699,23 @@
         public static final String SYNC_DATA1 = "sync_data1";
 
         /**
+         * This column is available for use by sync adapters
+         * <P>Type: TEXT</P>
+         */
+        public static final String SYNC_DATA7 = "sync_data7";
+
+        /**
+         * Used to indicate that a row is not a real event but an original copy of a locally
+         * modified event. A copy is made when an event changes from non-dirty to dirty and the
+         * event is on a calendar with {@link Calendars#CAN_PARTIALLY_UPDATE} set to 1. This copy
+         * does not get expanded in the instances table and is only visible in queries made by a
+         * sync adapter. The copy gets removed when the event is changed back to non-dirty by a
+         * sync adapter.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String LAST_SYNCED = "lastSynced";
+
+        /**
          * The comments feed uri. Column name.
          * TODO change to sync_data6
          * <P>Type: TEXT</P>
@@ -1030,7 +1060,9 @@
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
+                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, SYNC_DATA7);
                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
@@ -1191,7 +1223,8 @@
                 CAL_SYNC3,
                 CAL_SYNC4,
                 CAL_SYNC5,
-                CAL_SYNC6
+                CAL_SYNC6,
+                CAN_PARTIALLY_UPDATE,
         };
 
         /**
@@ -1706,9 +1739,9 @@
         public static final String NOTIFY_TIME = "notifyTime";
 
         /**
-         * The state of this alert. It starts out as {@link SCHEDULED}, then
-         * when the alarm goes off, it changes to {@link FIRED}, and then when
-         * the user dismisses the alarm it changes to {@link DISMISSED}. Column
+         * The state of this alert. It starts out as {@link #SCHEDULED}, then
+         * when the alarm goes off, it changes to {@link #FIRED}, and then when
+         * the user dismisses the alarm it changes to {@link #DISMISSED}. Column
          * name.
          * <P>Type: INTEGER</P>
          */
diff --git a/core/jni/android_app_backup_FullBackup.cpp b/core/jni/android_app_backup_FullBackup.cpp
index ecfe5ff..b36fa3e 100644
--- a/core/jni/android_app_backup_FullBackup.cpp
+++ b/core/jni/android_app_backup_FullBackup.cpp
@@ -73,6 +73,8 @@
 static int backupToTar(JNIEnv* env, jobject clazz, jstring packageNameObj,
         jstring domainObj, jstring linkdomain,
         jstring rootpathObj, jstring pathObj, jobject dataOutputObj) {
+    int ret;
+
     // Extract the various strings, allowing for null object pointers
     const char* packagenamechars = env->GetStringUTFChars(packageNameObj, NULL);
     const char* rootchars = env->GetStringUTFChars(rootpathObj, NULL);
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9c55627..94a9063 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5156,6 +5156,8 @@
         <!-- number of megabytes of storage MTP should reserve for free storage
              (used for emulated storage that is shared with system's data partition) -->
         <attr name="mtpReserve" format="integer" />
+        <!-- true if the storage can be shared via USB mass storage -->
+        <attr name="allowMassStorage" format="boolean" />
     </declare-styleable>
 
 </resources>
diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp
index f933199f..b433fd3 100644
--- a/libs/utils/BackupHelpers.cpp
+++ b/libs/utils/BackupHelpers.cpp
@@ -503,10 +503,10 @@
         needExtended = true;
     }
 
-    // Non-7bit-clean path also means needing pax extended format
+    // Non-7bit-clean path or embedded spaces also mean needing pax extended format
     if (!needExtended) {
         for (size_t i = 0; i < filepath.length(); i++) {
-            if ((filepath[i] & 0x80) != 0) {
+            if ((filepath[i] & 0x80) != 0 || filepath[i] == ' ') {
                 needExtended = true;
                 break;
             }
diff --git a/packages/SharedStorageBackup/Android.mk b/packages/SharedStorageBackup/Android.mk
new file mode 100644
index 0000000..1d4f4da7
--- /dev/null
+++ b/packages/SharedStorageBackup/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2011 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_PACKAGE_NAME := SharedStorageBackup
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
+########################
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/packages/SharedStorageBackup/AndroidManifest.xml b/packages/SharedStorageBackup/AndroidManifest.xml
new file mode 100644
index 0000000..258059c
--- /dev/null
+++ b/packages/SharedStorageBackup/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2011, 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.sharedstoragebackup" >
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
+
+    <application android:allowClearUserData="false"
+                 android:permission="android.permission.CONFIRM_FULL_BACKUP"
+                 android:fullBackupAgent=".SharedStorageAgent"
+                 android:allowBackup="false" >
+    </application>
+</manifest>
diff --git a/packages/SharedStorageBackup/proguard.flags b/packages/SharedStorageBackup/proguard.flags
new file mode 100644
index 0000000..f43cb81
--- /dev/null
+++ b/packages/SharedStorageBackup/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.sharedstoragebackup.SharedStorageAgent
diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
new file mode 100644
index 0000000..b02ca2e
--- /dev/null
+++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
@@ -0,0 +1,93 @@
+package com.android.sharedstoragebackup;
+
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.content.Context;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+public class SharedStorageAgent extends FullBackupAgent {
+    static final String TAG = "SharedStorageAgent";
+    static final boolean DEBUG = true;
+
+    StorageVolume[] mVolumes;
+
+    @Override
+    public void onCreate() {
+        StorageManager mgr = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
+        if (mgr != null) {
+            mVolumes = mgr.getVolumeList();
+        } else {
+            Slog.e(TAG, "Unable to access Storage Manager");
+        }
+    }
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) throws IOException {
+        // If there are shared-storage volumes available, run the inherited directory-
+        // hierarchy backup process on them.  By convention in the Storage Manager, the
+        // "primary" shared storage volume is first in the list.
+        if (mVolumes != null) {
+            for (int i = 0; i < mVolumes.length; i++) {
+                StorageVolume v = mVolumes[i];
+                // Express the contents of volume N this way in the tar stream:
+                //     shared/N/path/to/file
+                // The restore will then extract to the given volume
+                String domain = FullBackup.SHARED_PREFIX + i;
+                processTree(null, domain, v.getPath(), null, data);
+            }
+        }
+    }
+
+    /**
+     * Incremental onRestore() implementation is not used.
+     */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+            throws IOException {
+    }
+
+    /**
+     * Full restore of one file to shared storage
+     */
+    @Override
+    public void onRestoreFile(ParcelFileDescriptor data, long size,
+            int type, String domain, String relpath, long mode, long mtime)
+            throws IOException {
+        Slog.d(TAG, "Shared restore: [ " + domain + " : " + relpath + "]");
+
+        File outFile = null;
+
+        // The file path must be in the semantic form [number]/path/to/file...
+        int slash = relpath.indexOf('/');
+        if (slash > 0) {
+            try {
+                int i = Integer.parseInt(relpath.substring(0, slash));
+                if (i <= mVolumes.length) {
+                    outFile = new File(mVolumes[i].getPath(), relpath.substring(slash + 1));
+                    if (DEBUG) Slog.i(TAG, " => " + outFile.getAbsolutePath());
+                } else {
+                    Slog.w(TAG, "Cannot restore data for unavailable volume " + i);
+                }
+            } catch (NumberFormatException e) {
+                if (DEBUG) Slog.w(TAG, "Bad volume number token: " + relpath.substring(0, slash));
+            }
+        } else {
+            if (DEBUG) Slog.i(TAG, "Can't find volume-number token");
+        }
+        if (outFile == null) {
+            Slog.e(TAG, "Skipping data with malformed path " + relpath);
+        }
+
+        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, false);
+    }
+}
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_zoom_default.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_zoom_default.png
new file mode 100644
index 0000000..e2584e3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_zoom_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_zoom_pressed.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_zoom_pressed.png
new file mode 100644
index 0000000..58b8510
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_zoom_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_zoom_default.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_zoom_default.png
new file mode 100644
index 0000000..2795c34
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_zoom_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_zoom_pressed.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_zoom_pressed.png
new file mode 100644
index 0000000..bbed6a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_zoom_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_zoom.xml b/packages/SystemUI/res/drawable/ic_sysbar_zoom.xml
new file mode 100644
index 0000000..977e002
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_sysbar_zoom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/ic_sysbar_zoom_pressed" />
+    <item android:drawable="@drawable/ic_sysbar_zoom_default" />
+</selector>
+
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar.xml b/packages/SystemUI/res/layout-sw600dp/status_bar.xml
index d9f3f23..707a8cb 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar.xml
@@ -75,6 +75,13 @@
                     systemui:keyCode="82"
                     android:visibility="invisible"
                     />
+                <com.android.systemui.statusbar.policy.CompatModeButton
+                    android:id="@+id/compat_button"
+                    android:layout_width="80dip"
+                    android:layout_height="match_parent"
+                    android:src="@drawable/ic_sysbar_zoom"
+                    android:visibility="invisible"
+                    />
             </LinearLayout>
 
             <!-- fake space bar zone -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java
new file mode 100644
index 0000000..9b44f78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+public class CompatModeButton extends ImageView implements View.OnClickListener {
+    private static final String TAG = "StatusBar.CompatModeButton";
+
+    private ActivityManager mAM;
+
+    public CompatModeButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CompatModeButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+
+        setClickable(true);
+
+        mAM = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+        setOnClickListener(this);
+
+        refresh();
+    }
+
+    @Override
+    public void onClick(View v) {
+        mAM.setFrontActivityScreenCompatMode(ActivityManager.COMPAT_MODE_TOGGLE);
+    }
+
+    public void refresh() {
+        setVisibility(
+                (mAM.getFrontActivityScreenCompatMode() == ActivityManager.COMPAT_MODE_NEVER)
+                ? View.GONE
+                : View.VISIBLE
+            );
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 5b5801d..ffb45ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.*;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CompatModeButton;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.Prefs;
@@ -963,6 +964,10 @@
 
         // See above re: lights-out policy for legacy apps.
         if (visible) setLightsOn(true);
+
+        // XXX: HACK: not sure if this is the best way to catch a new activity that might require a
+        // change in compatibility features, but it's a start.
+        ((CompatModeButton) mBarContents.findViewById(R.id.compat_button)).refresh();
     }
 
     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 7c6d3c1..b568af1 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -134,6 +134,7 @@
     // Timeout intervals for agent backup & restore operations
     static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000;
     static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000;
+    static final long TIMEOUT_SHARED_BACKUP_INTERVAL = 30 * 60 * 1000;
     static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000;
 
     // User confirmation timeout for a full backup/restore operation
@@ -1691,7 +1692,7 @@
         public void run() {
             final List<PackageInfo> packagesToBackup;
 
-            Slog.i(TAG, "--- Performing full-dataset restore ---");
+            Slog.i(TAG, "--- Performing full-dataset backup ---");
             sendStartBackup();
 
             // doAllApps supersedes the package set if any
@@ -1720,64 +1721,23 @@
                 }
             }
 
-            // Now back up the app data via the agent mechanism
             PackageInfo pkg = null;
             try {
+                // Now back up the app data via the agent mechanism
                 int N = packagesToBackup.size();
                 for (int i = 0; i < N; i++) {
                     pkg = packagesToBackup.get(i);
+                    backupOnePackage(pkg);
+                }
 
-                    Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
-
-                    IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
-                            IApplicationThread.BACKUP_MODE_FULL);
-                    if (agent != null) {
-                        try {
-                            ApplicationInfo app = pkg.applicationInfo;
-                            boolean sendApk = mIncludeApks
-                                    && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0)
-                                    && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
-                                        (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
-
-                            sendOnBackupPackage(pkg.packageName);
-
-                            {
-                                BackupDataOutput output = new BackupDataOutput(
-                                        mOutputFile.getFileDescriptor());
-
-                                if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName);
-                                writeAppManifest(pkg, mManifestFile, sendApk);
-                                FullBackup.backupToTar(pkg.packageName, null, null,
-                                        mFilesDir.getAbsolutePath(),
-                                        mManifestFile.getAbsolutePath(),
-                                        output);
-                            }
-
-                            if (DEBUG) Slog.d(TAG, "Calling doBackup()");
-                            final int token = generateToken();
-                            prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL);
-                            agent.doBackup(null, mOutputFile, null, sendApk,
-                                    token, mBackupManagerBinder);
-                            boolean success = waitUntilOperationComplete(token);
-                            if (!success) {
-                                Slog.d(TAG, "Full backup failed on package " + pkg.packageName);
-                            } else {
-                                if (DEBUG) Slog.d(TAG, "Full backup success: " + pkg.packageName);
-                            }
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Error backing up " + pkg.packageName, e);
-                        }
-                    } else {
-                        Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName);
-                    }
-                    tearDown(pkg);
+                // Finally, shared storage if requested
+                if (mIncludeShared) {
+                    backupSharedStorage();
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "App died during full backup");
             } finally {
-                if (pkg != null) {
-                    tearDown(pkg);
-                }
+                tearDown(pkg);
                 try {
                     mOutputFile.close();
                 } catch (IOException e) {
@@ -1796,6 +1756,79 @@
             }
         }
 
+        private void backupOnePackage(PackageInfo pkg) throws RemoteException {
+            Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
+
+            IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
+                    IApplicationThread.BACKUP_MODE_FULL);
+            if (agent != null) {
+                try {
+                    ApplicationInfo app = pkg.applicationInfo;
+                    boolean sendApk = mIncludeApks
+                            && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0)
+                            && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
+                                (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+
+                    sendOnBackupPackage(pkg.packageName);
+
+                    {
+                        BackupDataOutput output = new BackupDataOutput(
+                                mOutputFile.getFileDescriptor());
+
+                        if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName);
+                        writeAppManifest(pkg, mManifestFile, sendApk);
+                        FullBackup.backupToTar(pkg.packageName, null, null,
+                                mFilesDir.getAbsolutePath(),
+                                mManifestFile.getAbsolutePath(),
+                                output);
+                    }
+
+                    if (DEBUG) Slog.d(TAG, "Calling doBackup()");
+                    final int token = generateToken();
+                    prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL);
+                    agent.doBackup(null, mOutputFile, null, sendApk,
+                            token, mBackupManagerBinder);
+                    if (!waitUntilOperationComplete(token)) {
+                        Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
+                    } else {
+                        if (DEBUG) Slog.d(TAG, "Full backup success: " + pkg.packageName);
+                    }
+                } catch (IOException e) {
+                    Slog.e(TAG, "Error backing up " + pkg.packageName, e);
+                }
+            } else {
+                Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName);
+            }
+            tearDown(pkg);
+        }
+
+        private void backupSharedStorage() throws RemoteException {
+            PackageInfo pkg = null;
+            try {
+                pkg = mPackageManager.getPackageInfo("com.android.sharedstoragebackup", 0);
+                IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
+                        IApplicationThread.BACKUP_MODE_FULL);
+                if (agent != null) {
+                    sendOnBackupPackage("Shared storage");
+
+                    final int token = generateToken();
+                    prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL);
+                    agent.doBackup(null, mOutputFile, null, false, token, mBackupManagerBinder);
+                    if (!waitUntilOperationComplete(token)) {
+                        Slog.e(TAG, "Full backup failed on shared storage");
+                    } else {
+                        if (DEBUG) Slog.d(TAG, "Full shared storage backup success");
+                    }
+                } else {
+                    Slog.e(TAG, "Could not bind to shared storage backup agent");
+                }
+            } catch (NameNotFoundException e) {
+                Slog.e(TAG, "Shared storage backup package not found");
+            } finally {
+                tearDown(pkg);
+            }
+        }
+
         private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk)
                 throws IOException {
             // Manifest format. All data are strings ending in LF:
@@ -1836,23 +1869,24 @@
         }
 
         private void tearDown(PackageInfo pkg) {
-            final ApplicationInfo app = pkg.applicationInfo;
-            try {
-                // unbind and tidy up even on timeout or failure, just in case
-                mActivityManager.unbindBackupAgent(app);
+            if (pkg != null) {
+                final ApplicationInfo app = pkg.applicationInfo;
+                if (app != null) {
+                    try {
+                        // unbind and tidy up even on timeout or failure, just in case
+                        mActivityManager.unbindBackupAgent(app);
 
-                // The agent was running with a stub Application object, so shut it down.
-                // !!! We hardcode the confirmation UI's package name here rather than use a
-                //     manifest flag!  TODO something less direct.
-                if (app.uid != Process.SYSTEM_UID
-                        && !pkg.packageName.equals("com.android.backupconfirm")) {
-                    if (DEBUG) Slog.d(TAG, "Backup complete, killing host process");
-                    mActivityManager.killApplicationProcess(app.processName, app.uid);
-                } else {
-                    if (DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName);
+                        // The agent was running with a stub Application object, so shut it down.
+                        if (app.uid != Process.SYSTEM_UID) {
+                            if (DEBUG) Slog.d(TAG, "Backup complete, killing host process");
+                            mActivityManager.killApplicationProcess(app.processName, app.uid);
+                        } else {
+                            if (DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.d(TAG, "Lost app trying to shut down");
+                    }
                 }
-            } catch (RemoteException e) {
-                Slog.d(TAG, "Lost app trying to shut down");
             }
         }
 
@@ -1949,6 +1983,7 @@
             // with a whitelist of packages known to be unclearable.
             mClearedPackages.add("android");
             mClearedPackages.add("com.android.providers.settings");
+
         }
 
         class RestoreFileRunnable implements Runnable {
@@ -1988,6 +2023,11 @@
             Slog.i(TAG, "--- Performing full-dataset restore ---");
             sendStartRestore();
 
+            // Are we able to restore shared-storage data?
+            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+                mPackagePolicies.put("com.android.sharedstoragebackup", RestorePolicy.ACCEPT);
+            }
+
             try {
                 byte[] buffer = new byte[32 * 1024];
                 FileInputStream instream = new FileInputStream(mInputFile.getFileDescriptor());
@@ -2707,7 +2747,9 @@
                         info.path, 0, FullBackup.SHARED_PREFIX.length())) {
                     // File in shared storage.  !!! TODO: implement this.
                     info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
+                    info.packageName = "com.android.sharedstoragebackup";
                     info.domain = FullBackup.SHARED_STORAGE_TOKEN;
+                    if (DEBUG) Slog.i(TAG, "File in shared storage: " + info.path);
                 } else if (FullBackup.APPS_PREFIX.regionMatches(0,
                         info.path, 0, FullBackup.APPS_PREFIX.length())) {
                     // App content!  Parse out the package name and domain
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index f78dca9..c86f962 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1121,16 +1121,20 @@
                             com.android.internal.R.styleable.Storage_emulated, false);
                     int mtpReserve = a.getInt(
                             com.android.internal.R.styleable.Storage_mtpReserve, 0);
+                    boolean allowMassStorage = a.getBoolean(
+                            com.android.internal.R.styleable.Storage_allowMassStorage, false);
 
                     Slog.d(TAG, "got storage path: " + path + " description: " + description +
                             " primary: " + primary + " removable: " + removable +
-                            " emulated: " + emulated +  " mtpReserve: " + mtpReserve);
+                            " emulated: " + emulated +  " mtpReserve: " + mtpReserve +
+                            " allowMassStorage: " + allowMassStorage);
                     if (path == null || description == null) {
                         Slog.e(TAG, "path or description is null in readStorageList");
                     } else {
                         String pathString = path.toString();
                         StorageVolume volume = new StorageVolume(pathString,
-                                description.toString(), removable, emulated, mtpReserve);
+                                description.toString(), removable, emulated,
+                                mtpReserve, allowMassStorage);
                         if (primary) {
                             if (mPrimaryVolume == null) {
                                 mPrimaryVolume = volume;
diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java
index 99c8af6..08c6699 100644
--- a/services/java/com/android/server/SystemBackupAgent.java
+++ b/services/java/com/android/server/SystemBackupAgent.java
@@ -138,7 +138,7 @@
             if (outFile == null) {
                 Slog.w(TAG, "Skipping unrecognized system file: [ " + domain + " : " + path + " ]");
             }
-            FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
+            FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true);
 
             if (restoredWallpaper) {
                 WallpaperManagerService wallpaper =