Merge change 3800 into donut

* changes:
  Add SUGGEST_SPINNER_WHILE_REFRESHING column constant to the SearchManager for use in implementing Google suggestion refreshing in global search.
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index c3b6a02..8df7eae 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -45,7 +45,7 @@
     /**
      * Defined backup transports understood by {@link IBackupManager.selectBackupTransport}.
      */
-    public static final int TRANSPORT_ADB = 1;
+    public static final int TRANSPORT_LOCAL = 1;
     public static final int TRANSPORT_GOOGLE = 2;
 
     /**
diff --git a/core/java/com/android/internal/backup/AdbTransport.java b/core/java/com/android/internal/backup/AdbTransport.java
deleted file mode 100644
index 8d3bd1c..0000000
--- a/core/java/com/android/internal/backup/AdbTransport.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.android.internal.backup;
-
-import android.backup.RestoreSet;
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-
-/**
- * Backup transport for full backup over adb.  This transport pipes everything to
- * a file in a known location in /cache, which 'adb backup' then pulls to the desktop
- * (deleting it afterwards).
- */
-
-public class AdbTransport extends IBackupTransport.Stub {
-
-    public long requestBackupTime() throws RemoteException {
-        return 0;
-    }
-
-    public int startSession() throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    public int endSession() throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
-            throws RemoteException {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    // Restore handling
-    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
-        RestoreSet[] set = new RestoreSet[1];
-        set[0].device = "USB";
-        set[0].name = "adb";
-        set[0].token = 0;
-        return set;
-    }
-
-    public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
-        // !!! TODO: real implementation
-        return new PackageInfo[0];
-    }
-
-    public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor data)
-            throws android.os.RemoteException {
-        // !!! TODO: real implementation
-        return 0;
-    }
-}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
new file mode 100644
index 0000000..62fba4a
--- /dev/null
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -0,0 +1,140 @@
+package com.android.internal.backup;
+
+import android.backup.RestoreSet;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Backup transport for stashing stuff into a known location on disk, and
+ * later restoring from there.  For testing only.
+ */
+
+public class LocalTransport extends IBackupTransport.Stub {
+    private static final String TAG = "LocalTransport";
+    private static final String DATA_FILE_NAME = "data";
+
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
+    private FileFilter mDirFileFilter = new FileFilter() {
+        public boolean accept(File f) {
+            return f.isDirectory();
+        }
+    };
+
+
+    public LocalTransport(Context context) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+    }
+
+    public long requestBackupTime() throws RemoteException {
+        // any time is a good time for local backup
+        return 0;
+    }
+
+    public int startSession() throws RemoteException {
+        return 0;
+    }
+
+    public int endSession() throws RemoteException {
+        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
+
+        // Parse out the backup data into the key/value updates
+
+        // Apply the backup key/value updates to the image
+
+        // Write out the image in the canonical format
+
+        return -1;
+    }
+
+    // Restore handling
+    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+        // one hardcoded restore set
+        RestoreSet[] set = new RestoreSet[1];
+        set[0].device = "flash";
+        set[0].name = "Local disk image";
+        set[0].token = 0;
+        return set;
+    }
+
+    public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
+        // the available packages are the extant subdirs of mDatadir
+        File[] packageDirs = mDataDir.listFiles(mDirFileFilter);
+        ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>();
+        for (File dir : packageDirs) {
+            try {
+                PackageInfo pkg = mPackageManager.getPackageInfo(dir.getName(),
+                        PackageManager.GET_SIGNATURES);
+                if (pkg != null) {
+                    packages.add(pkg);
+                }
+            } catch (NameNotFoundException e) {
+                // restore set contains data for a package not installed on the
+                // phone -- just ignore it.
+            }
+        }
+
+        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()];
+        return packages.toArray(result);
+    }
+
+    public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor output)
+            throws android.os.RemoteException {
+        // 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);
+    }
+
+    private int copyFileToFD(File source, ParcelFileDescriptor dest) {
+        try {
+            FileInputStream in = new FileInputStream(source);
+            FileOutputStream out = new FileOutputStream(dest.getFileDescriptor());
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            // something went wrong; claim failure
+            return -1;
+        }
+        return 0;
+    }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 99a381c..16a3bad 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -30,9 +30,11 @@
 import android.util.Printer;
 import android.util.SparseArray;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -53,7 +55,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS' 
 
     // Current on-disk Parcel version
-    private static final int VERSION = 38;
+    private static final int VERSION = 39;
 
     private final File mFile;
     private final File mBackupFile;
@@ -94,7 +96,7 @@
     
     boolean mScreenOn;
     StopwatchTimer mScreenOnTimer;
-    
+
     int mScreenBrightnessBin = -1;
     final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
     
@@ -137,10 +139,10 @@
     long mTrackBatteryUptimeStart;
     long mTrackBatteryPastRealtime;
     long mTrackBatteryRealtimeStart;
-    
+
     long mUnpluggedBatteryUptime;
     long mUnpluggedBatteryRealtime;
-    
+
     /*
      * These keep track of battery levels (1-100) at the last plug event and the last unplug event.
      */
@@ -149,6 +151,15 @@
 
     long mLastWriteTime = 0; // Milliseconds
 
+    // Mobile data transferred while on battery
+    private long[] mMobileDataTx = new long[4];
+    private long[] mMobileDataRx = new long[4];
+    private long[] mTotalDataTx = new long[4];
+    private long[] mTotalDataRx = new long[4];
+
+    private long mRadioDataUptime;
+    private long mRadioDataStart;
+
     /*
      * Holds a SamplingTimer associated with each kernel wakelock name being tracked.
      */
@@ -893,7 +904,40 @@
         }
         return kwlt;
     }
-    
+
+    private void doDataPlug(long[] dataTransfer, long currentBytes) {
+        dataTransfer[STATS_LAST] = dataTransfer[STATS_UNPLUGGED];
+        dataTransfer[STATS_UNPLUGGED] = -1;
+    }
+
+    private void doDataUnplug(long[] dataTransfer, long currentBytes) {
+        dataTransfer[STATS_UNPLUGGED] = currentBytes;
+    }
+
+    private long getCurrentRadioDataUptimeMs() {
+        try {
+            File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms");
+            if (!awakeTimeFile.exists()) return 0;
+            BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile));
+            String line = br.readLine();
+            br.close();
+            return Long.parseLong(line);
+        } catch (NumberFormatException nfe) {
+            // Nothing
+        } catch (IOException ioe) {
+            // Nothing
+        }
+        return 0;
+    }
+
+    public long getRadioDataUptimeMs() {
+        if (mRadioDataStart == -1) {
+            return mRadioDataUptime;
+        } else {
+            return getCurrentRadioDataUptimeMs() - mRadioDataStart;
+        }
+    }
+
     public void doUnplug(long batteryUptime, long batteryRealtime) {
         for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
             Uid u = mUidStats.valueAt(iu);
@@ -905,8 +949,16 @@
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
             mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
         }
+        // Track total mobile data
+        doDataUnplug(mMobileDataRx, NetStat.getMobileRxBytes());
+        doDataUnplug(mMobileDataTx, NetStat.getMobileTxBytes());
+        doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes());
+        doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes());
+        // Track radio awake time
+        mRadioDataStart = getCurrentRadioDataUptimeMs();
+        mRadioDataUptime = 0;
     }
-    
+
     public void doPlug(long batteryUptime, long batteryRealtime) {
         for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
             Uid u = mUidStats.valueAt(iu);
@@ -922,8 +974,15 @@
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
             mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
         }
+        doDataPlug(mMobileDataRx, NetStat.getMobileRxBytes());
+        doDataPlug(mMobileDataTx, NetStat.getMobileTxBytes());
+        doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes());
+        doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes());
+        // Track radio awake time
+        mRadioDataUptime = getRadioDataUptimeMs();
+        mRadioDataStart = -1;
     }
-    
+
     public void noteStartGps(int uid) {
         mUidStats.get(uid).noteStartGps();
     }
@@ -931,7 +990,7 @@
     public void noteStopGps(int uid) {
         mUidStats.get(uid).noteStopGps();
     }
-    
+
     public void noteScreenOnLocked() {
         if (!mScreenOn) {
             mScreenOn = true;
@@ -1039,6 +1098,7 @@
                     break;
             }
         }
+        if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
         if (mPhoneDataConnectionType != bin) {
             if (mPhoneDataConnectionType >= 0) {
                 mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this);
@@ -2701,7 +2761,44 @@
     public long getBatteryRealtime(long curTime) {
         return getBatteryRealtimeLocked(curTime);
     }
-    
+
+    private long getTcpBytes(long current, long[] dataBytes, int which) {
+        if (which == STATS_LAST) {
+            return dataBytes[STATS_LAST];
+        } else {
+            if (which == STATS_UNPLUGGED) {
+                if (dataBytes[STATS_UNPLUGGED] < 0) {
+                    return dataBytes[STATS_LAST];
+                } else {
+                    return current - dataBytes[STATS_UNPLUGGED];
+                }
+            } else if (which == STATS_TOTAL) {
+                return (current - dataBytes[STATS_CURRENT]) + dataBytes[STATS_TOTAL];
+            }
+            return current - dataBytes[STATS_CURRENT];
+        }
+    }
+
+    /** Only STATS_UNPLUGGED works properly */
+    public long getMobileTcpBytesSent(int which) {
+        return getTcpBytes(NetStat.getMobileTxBytes(), mMobileDataTx, which);
+    }
+
+    /** Only STATS_UNPLUGGED works properly */
+    public long getMobileTcpBytesReceived(int which) {
+        return getTcpBytes(NetStat.getMobileRxBytes(), mMobileDataRx, which);
+    }
+
+    /** Only STATS_UNPLUGGED works properly */
+    public long getTotalTcpBytesSent(int which) {
+        return getTcpBytes(NetStat.getTotalTxBytes(), mTotalDataTx, which);
+    }
+
+    /** Only STATS_UNPLUGGED works properly */
+    public long getTotalTcpBytesReceived(int which) {
+        return getTcpBytes(NetStat.getTotalRxBytes(), mTotalDataRx, which);
+    }
+
     @Override
     public int getDischargeStartLevel() {
         synchronized(this) {
@@ -3227,6 +3324,18 @@
         mDischargeCurrentLevel = in.readInt();
         mLastWriteTime = in.readLong();
 
+        mMobileDataRx[STATS_LAST] = in.readLong();
+        mMobileDataRx[STATS_UNPLUGGED] = -1;
+        mMobileDataTx[STATS_LAST] = in.readLong();
+        mMobileDataTx[STATS_UNPLUGGED] = -1;
+        mTotalDataRx[STATS_LAST] = in.readLong();
+        mTotalDataRx[STATS_UNPLUGGED] = -1;
+        mTotalDataTx[STATS_LAST] = in.readLong();
+        mTotalDataTx[STATS_UNPLUGGED] = -1;
+
+        mRadioDataUptime = in.readLong();
+        mRadioDataStart = -1;
+
         mKernelWakelockStats.clear();
         int NKW = in.readInt();
         for (int ikw = 0; ikw < NKW; ikw++) {
@@ -3301,6 +3410,14 @@
         out.writeInt(mDischargeCurrentLevel);
         out.writeLong(mLastWriteTime);
 
+        out.writeLong(getMobileTcpBytesReceived(STATS_UNPLUGGED));
+        out.writeLong(getMobileTcpBytesSent(STATS_UNPLUGGED));
+        out.writeLong(getTotalTcpBytesReceived(STATS_UNPLUGGED));
+        out.writeLong(getTotalTcpBytesSent(STATS_UNPLUGGED));
+
+        // Write radio uptime for data
+        out.writeLong(getRadioDataUptimeMs());
+
         out.writeInt(mKernelWakelockStats.size());
         for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
             SamplingTimer kwlt = ent.getValue();
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index f08dddd..a37bf6e 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -26,6 +26,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -118,26 +119,28 @@
      */
     public static final String POWER_VIDEO = "dsp.video";
 
-    static final HashMap<String, Double> sPowerMap = new HashMap<String, Double>();
+    static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>();
 
     private static final String TAG_DEVICE = "device";
     private static final String TAG_ITEM = "item";
+    private static final String TAG_ARRAY = "array";
+    private static final String TAG_ARRAYITEM = "value";
     private static final String ATTR_NAME = "name";
 
-    public PowerProfile(Context context, CharSequence profile) {
+    public PowerProfile(Context context) {
         // Read the XML file for the given profile (normally only one per
         // device)
         if (sPowerMap.size() == 0) {
-            readPowerValuesFromXml(context, profile);
+            readPowerValuesFromXml(context);
         }
     }
 
-    private void readPowerValuesFromXml(Context context, CharSequence profile) {
-        // FIXME
-        //int id = context.getResources().getIdentifier(profile.toString(), "xml", 
-        //        "com.android.internal");
-        int id = com.android.internal.R.xml.power_profile_default;
+    private void readPowerValuesFromXml(Context context) {
+        int id = com.android.internal.R.xml.power_profile;
         XmlResourceParser parser = context.getResources().getXml(id);
+        boolean parsingArray = false;
+        ArrayList<Double> array = new ArrayList<Double>();
+        String arrayName = null;
 
         try {
             XmlUtils.beginDocument(parser, TAG_DEVICE);
@@ -145,22 +148,39 @@
             while (true) {
                 XmlUtils.nextElement(parser);
 
-                String element = parser.getName(); 
-                if (element == null || !(element.equals(TAG_ITEM))) {
-                    break;
+                String element = parser.getName();
+                if (element == null) break;
+                
+                if (parsingArray && !element.equals(TAG_ARRAYITEM)) {
+                    // Finish array
+                    sPowerMap.put(arrayName, array.toArray(new Double[array.size()]));
+                    parsingArray = false;
                 }
-
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                if (parser.next() == XmlPullParser.TEXT) {
-                    String power = parser.getText();
-                    double value = 0;
-                    try {
-                        value = Double.valueOf(power);
-                    } catch (NumberFormatException nfe) {
+                if (element.equals(TAG_ARRAY)) {
+                    parsingArray = true;
+                    array.clear();
+                    arrayName = parser.getAttributeValue(null, ATTR_NAME);
+                } else if (element.equals(TAG_ITEM) || element.equals(TAG_ARRAYITEM)) {
+                    String name = null;
+                    if (!parsingArray) name = parser.getAttributeValue(null, ATTR_NAME);
+                    if (parser.next() == XmlPullParser.TEXT) {
+                        String power = parser.getText();
+                        double value = 0;
+                        try {
+                            value = Double.valueOf(power);
+                        } catch (NumberFormatException nfe) {
+                        }
+                        if (element.equals(TAG_ITEM)) {
+                            sPowerMap.put(name, value);
+                        } else if (parsingArray) {
+                            array.add(value);
+                        }
                     }
-                    sPowerMap.put(name, value);
                 }
             }
+            if (parsingArray) {
+                sPowerMap.put(arrayName, array.toArray(new Double[array.size()]));
+            }
         } catch (XmlPullParserException e) {
             throw new RuntimeException(e);
         } catch (IOException e) {
@@ -177,7 +197,40 @@
      */
     public double getAveragePower(String type) {
         if (sPowerMap.containsKey(type)) {
-            return sPowerMap.get(type);
+            Object data = sPowerMap.get(type);
+            if (data instanceof Double[]) {
+                return ((Double[])data)[0];
+            } else {
+                return (Double) sPowerMap.get(type);
+            }
+        } else {
+            return 0;
+        }
+    }
+    
+    /**
+     * Returns the average current in mA consumed by the subsystem for the given level. 
+     * @param type the subsystem type
+     * @param level the level of power at which the subsystem is running. For instance, the
+     *  signal strength of the cell network between 0 and 4 (if there are 4 bars max.).
+     *  If there is no data for multiple levels, the level is ignored.
+     * @return the average current in milliAmps.
+     */
+    public double getAveragePower(String type, int level) {
+        if (sPowerMap.containsKey(type)) {
+            Object data = sPowerMap.get(type);
+            if (data instanceof double[]) {
+                final double[] values = (double[]) data;
+                if (values.length > level) {
+                    return values[level];
+                } else if (values.length < 0) {
+                    return values[0];
+                } else {
+                    return values[values.length - 1];
+                }
+            } else {
+                return (Double) data;
+            }
         } else {
             return 0;
         }
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
new file mode 100644
index 0000000..859902e
--- /dev/null
+++ b/core/res/res/xml/power_profile.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<device name="Android">
+  <item name="none">0</item>
+  <item name="screen.on">0.1</item>
+  <item name="bluetooth.active">0.1</item>
+  <item name="bluetooth.on">0.1</item>
+  <item name="screen.full">0.1</item>
+  <item name="wifi.on">0.1</item>
+  <item name="wifi.active">0.1</item>
+  <item name="wifi.scan">0.1</item>
+  <item name="cpu.idle">0.1</item>
+  <item name="cpu.normal">0.2</item>
+  <item name="cpu.full">1</item>
+  <item name="dsp.audio">0.1</item>
+  <item name="dsp.video">0.1</item>
+  <item name="radio.active">1</item>
+  <item name="gps.on">1</item>
+  <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
+      <value>1</value>
+      <value>0.1</value>
+  </array>
+</device>
diff --git a/core/res/res/xml/power_profile_default.xml b/core/res/res/xml/power_profile_default.xml
deleted file mode 100644
index ceecb1a..0000000
--- a/core/res/res/xml/power_profile_default.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License")
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<device name="Android">
-  <item name="none">0</item>
-  <item name="screen.on">30</item>
-  <item name="bluetooth.active">103</item>
-  <item name="bluetooth.on">5</item>
-  <item name="screen.full">114</item>
-  <item name="wifi.on">23</item>
-  <item name="wifi.active">200</item>
-  <item name="wifi.scan">200</item>
-  <item name="cpu.idle">1.6</item>
-  <item name="cpu.normal">100</item>
-  <item name="cpu.full">140</item>
-  <item name="dsp.audio">70</item>
-  <item name="dsp.video">100</item>
-  <item name="radio.on">3</item>
-  <item name="radio.active">175</item>
-  <item name="gps.on">120</item>
-</device>
diff --git a/docs/html/search.jd b/docs/html/search.jd
index 0a802a6..defba30 100644
--- a/docs/html/search.jd
+++ b/docs/html/search.jd
@@ -2,7 +2,7 @@
 @jd:body

 

 <script src="http://www.google.com/jsapi" type="text/javascript"></script>

-<script src="/assets/jquery-history.js" type="text/javascript"></script>

+<script src="{@docRoot}assets/jquery-history.js" type="text/javascript"></script>

 <script type="text/javascript">

       google.load('search', '1');

 

diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
index e01bd53..ea42f53 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
@@ -80,7 +80,6 @@
       assertTrue("MIDI getDuration", duratoinWithinTolerence);  
     }
     
-    @Suppress
     @MediumTest
     public void testWMA9GetDuration() throws Exception {
       int duration = CodecTest.getDuration(MediaNames.WMA9); 
@@ -122,7 +121,6 @@
       assertTrue("MIDI GetCurrentPosition", currentPosition);  
     }
     
-    @Suppress
     @LargeTest
     public void testWMA9GetCurrentPosition() throws Exception {
       boolean currentPosition = CodecTest.getCurrentPosition(MediaNames.WMA9);  
@@ -160,7 +158,6 @@
       assertTrue("MIDI Pause", isPaused);  
     }
    
-    @Suppress
     @LargeTest
     public void testWMA9Pause() throws Exception {
       boolean isPaused = CodecTest.pause(MediaNames.WMA9);  
@@ -232,7 +229,6 @@
       assertTrue("MIDI setLooping", isLoop);  
     }
     
-    @Suppress
     @LargeTest
     public void testWMA9SetLooping() throws Exception {
       boolean isLoop = CodecTest.setLooping(MediaNames.WMA9);  
@@ -271,7 +267,6 @@
       assertTrue("MIDI seekTo", isLoop);  
     }
     
-    @Suppress
     @LargeTest
     public void testWMA9SeekTo() throws Exception {
       boolean isLoop = CodecTest.seekTo(MediaNames.WMA9);  
@@ -310,7 +305,7 @@
       boolean isEnd = CodecTest.seekToEnd(MediaNames.MIDI);  
       assertTrue("MIDI seekToEnd", isEnd);  
     }
-    
+
     @Suppress
     @LargeTest
     public void testWMA9SeekToEnd() throws Exception {
@@ -388,7 +383,6 @@
       assertTrue("H264AMR SeekTo", isSeek);         
     }
    
-    @Suppress
     @LargeTest
     public void testVideoWMVSeekTo() throws Exception {
       boolean isSeek = CodecTest.videoSeekTo(MediaNames.VIDEO_WMV);
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
index c2c76fd..8a64113 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -117,16 +117,7 @@
         // app.
         prefs = PreferenceManager.getDefaultSharedPreferences(this);
 
-        PackageManager pm = this.getPackageManager();
-        String soLibPath = "";
-        try {
-            soLibPath = pm.getApplicationInfo("com.svox.pico", 0).dataDir;
-        } catch (NameNotFoundException e) {
-            // This exception cannot actually happen as com.svox.pico is
-            // included with the system image.
-            e.printStackTrace();
-        }
-        soLibPath = soLibPath + "/lib/libttspico.so";
+        String soLibPath = "/system/lib/libttspico.so";
         nativeSynth = new SynthProxy(soLibPath);
 
         mSelf = this;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 42a895c..d3067ec 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -46,13 +47,14 @@
 import android.backup.BackupManager;
 import android.backup.RestoreSet;
 
-import com.android.internal.backup.AdbTransport;
+import com.android.internal.backup.LocalTransport;
 import com.android.internal.backup.GoogleTransport;
 import com.android.internal.backup.IBackupTransport;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.String;
 import java.util.ArrayList;
@@ -70,7 +72,11 @@
 
     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;
+
+    // Timeout interval for deciding that a bind or clear-data has taken too long
+    static final long TIMEOUT_INTERVAL = 10 * 1000;
+
     private Context mContext;
     private PackageManager mPackageManager;
     private final IActivityManager mActivityManager;
@@ -108,6 +114,10 @@
     private IBackupAgent mConnectedAgent;
     private volatile boolean mConnecting;
 
+    // A similar synchronicity mechanism around clearing apps' data for restore
+    private final Object mClearDataLock = new Object();
+    private volatile boolean mClearingData;
+
     private int mTransportId;
 
     private File mStateDir;
@@ -122,7 +132,9 @@
         mStateDir = new File(Environment.getDataDirectory(), "backup");
         mStateDir.mkdirs();
         mDataDir = Environment.getDownloadCacheDirectory();
-        mTransportId = BackupManager.TRANSPORT_GOOGLE;
+
+        //!!! TODO: default to cloud transport, not local
+        mTransportId = BackupManager.TRANSPORT_LOCAL;
         
         // Build our mapping of uid to backup client services
         synchronized (mBackupParticipants) {
@@ -203,81 +215,15 @@
 
             case MSG_RUN_FULL_BACKUP:
                 break;
+
+            case MSG_RUN_RESTORE:
+            {
+                int token = msg.arg1;
+                IBackupTransport transport = (IBackupTransport)msg.obj;
+                (new PerformRestoreThread(transport, token)).run();
+                break;
             }
-        }
-    }
-
-    void processOneBackup(BackupRequest request, IBackupAgent agent, IBackupTransport transport) {
-        final String packageName = request.appInfo.packageName;
-        Log.d(TAG, "processOneBackup doBackup() on " + packageName);
-
-        try {
-            // Look up the package info & signatures.  This is first so that if it
-            // throws an exception, there's no file setup yet that would need to
-            // be unraveled.
-            PackageInfo packInfo = mPackageManager.getPackageInfo(packageName,
-                    PackageManager.GET_SIGNATURES);
-
-            // !!! TODO: get the state file dir from the transport
-            File savedStateName = new File(mStateDir, packageName);
-            File backupDataName = new File(mDataDir, packageName + ".data");
-            File newStateName = new File(mStateDir, packageName + ".new");
-            
-            // In a full backup, we pass a null ParcelFileDescriptor as
-            // the saved-state "file"
-            ParcelFileDescriptor savedState = (request.fullBackup) ? null
-                    : ParcelFileDescriptor.open(savedStateName,
-                        ParcelFileDescriptor.MODE_READ_ONLY |
-                        ParcelFileDescriptor.MODE_CREATE);
-
-            backupDataName.delete();
-            ParcelFileDescriptor backupData =
-                    ParcelFileDescriptor.open(backupDataName,
-                            ParcelFileDescriptor.MODE_READ_WRITE |
-                            ParcelFileDescriptor.MODE_CREATE);
-
-            newStateName.delete();
-            ParcelFileDescriptor newState =
-                    ParcelFileDescriptor.open(newStateName,
-                            ParcelFileDescriptor.MODE_READ_WRITE |
-                            ParcelFileDescriptor.MODE_CREATE);
-
-            // Run the target's backup pass
-            boolean success = false;
-            try {
-                agent.doBackup(savedState, backupData, newState);
-                success = true;
-            } finally {
-                if (savedState != null) {
-                    savedState.close();
-                }
-                backupData.close();
-                newState.close();
             }
-
-            // Now propagate the newly-backed-up data to the transport
-            if (success) {
-                if (DEBUG) Log.v(TAG, "doBackup() success; calling transport");
-                backupData =
-                    ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
-                int error = transport.performBackup(packInfo, backupData);
-
-                // !!! TODO: After successful transport, delete the now-stale data
-                // and juggle the files so that next time the new state is passed
-                //backupDataName.delete();
-                newStateName.renameTo(savedStateName);
-            }
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Package not found on backup: " + packageName);
-        } catch (FileNotFoundException fnf) {
-            Log.w(TAG, "File not found on backup: ");
-            fnf.printStackTrace();
-        } catch (RemoteException e) {
-            Log.d(TAG, "Remote target " + request.appInfo.packageName + " threw during backup:");
-            e.printStackTrace();
-        } catch (Exception e) {
-            Log.w(TAG, "Final exception guard in backup: ");
-            e.printStackTrace();
         }
     }
 
@@ -396,9 +342,9 @@
     private IBackupTransport createTransport(int transportID) {
         IBackupTransport transport = null;
         switch (transportID) {
-        case BackupManager.TRANSPORT_ADB:
-            if (DEBUG) Log.v(TAG, "Initializing adb transport");
-            transport = new AdbTransport();
+        case BackupManager.TRANSPORT_LOCAL:
+            if (DEBUG) Log.v(TAG, "Initializing local transport");
+            transport = new LocalTransport(mContext);
             break;
 
         case BackupManager.TRANSPORT_GOOGLE:
@@ -424,11 +370,14 @@
                     Log.d(TAG, "awaiting agent for " + app);
 
                     // success; wait for the agent to arrive
-                    while (mConnecting && mConnectedAgent == null) {
+                    // only wait 10 seconds for the clear data to happen
+                    long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
+                    while (mConnecting && mConnectedAgent == null
+                            && (System.currentTimeMillis() < timeoutMark)) {
                         try {
-                            mAgentConnectLock.wait(10000);
+                            mAgentConnectLock.wait(5000);
                         } catch (InterruptedException e) {
-                            // just retry
+                            // just bail
                             return null;
                         }
                     }
@@ -447,6 +396,37 @@
         return agent;
     }
 
+    // clear an application's data, blocking until the operation completes or times out
+    void clearApplicationDataSynchronous(String packageName) {
+        ClearDataObserver observer = new ClearDataObserver();
+
+        synchronized(mClearDataLock) {
+            mClearingData = true;
+            mPackageManager.clearApplicationUserData(packageName, observer);
+
+            // only wait 10 seconds for the clear data to happen
+            long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
+            while (mClearingData && (System.currentTimeMillis() < timeoutMark)) {
+                try {
+                    mClearDataLock.wait(5000);
+                } catch (InterruptedException e) {
+                    // won't happen, but still.
+                    mClearingData = false;
+                }
+            }
+        }
+    }
+
+    class ClearDataObserver extends IPackageDataObserver.Stub {
+        public void onRemoveCompleted(String packageName, boolean succeeded)
+                throws android.os.RemoteException {
+            synchronized(mClearDataLock) {
+                mClearingData = false;
+                notifyAll();
+            }
+        }
+    }
+
     // ----- Back up a set of applications via a worker thread -----
 
     class PerformBackupThread extends Thread {
@@ -508,13 +488,88 @@
                     mActivityManager.unbindBackupAgent(request.appInfo);
                 } catch (SecurityException ex) {
                     // Try for the next one.
-                    Log.d(TAG, "error in bind", ex);
+                    Log.d(TAG, "error in bind/backup", ex);
                 } catch (RemoteException e) {
-                    // can't happen
+                    Log.v(TAG, "bind/backup threw");
+                    e.printStackTrace();
                 }
 
             }
         }
+
+        void processOneBackup(BackupRequest request, IBackupAgent agent, IBackupTransport transport) {
+            final String packageName = request.appInfo.packageName;
+            Log.d(TAG, "processOneBackup doBackup() on " + packageName);
+
+            try {
+                // Look up the package info & signatures.  This is first so that if it
+                // throws an exception, there's no file setup yet that would need to
+                // be unraveled.
+                PackageInfo packInfo = mPackageManager.getPackageInfo(packageName,
+                        PackageManager.GET_SIGNATURES);
+
+                // !!! TODO: get the state file dir from the transport
+                File savedStateName = new File(mStateDir, packageName);
+                File backupDataName = new File(mDataDir, packageName + ".data");
+                File newStateName = new File(mStateDir, packageName + ".new");
+
+                // In a full backup, we pass a null ParcelFileDescriptor as
+                // the saved-state "file"
+                ParcelFileDescriptor savedState = (request.fullBackup) ? null
+                        : ParcelFileDescriptor.open(savedStateName,
+                            ParcelFileDescriptor.MODE_READ_ONLY |
+                            ParcelFileDescriptor.MODE_CREATE);
+
+                backupDataName.delete();
+                ParcelFileDescriptor backupData =
+                        ParcelFileDescriptor.open(backupDataName,
+                                ParcelFileDescriptor.MODE_READ_WRITE |
+                                ParcelFileDescriptor.MODE_CREATE);
+
+                newStateName.delete();
+                ParcelFileDescriptor newState =
+                        ParcelFileDescriptor.open(newStateName,
+                                ParcelFileDescriptor.MODE_READ_WRITE |
+                                ParcelFileDescriptor.MODE_CREATE);
+
+                // Run the target's backup pass
+                boolean success = false;
+                try {
+                    agent.doBackup(savedState, backupData, newState);
+                    success = true;
+                } finally {
+                    if (savedState != null) {
+                        savedState.close();
+                    }
+                    backupData.close();
+                    newState.close();
+                }
+
+                // Now propagate the newly-backed-up data to the transport
+                if (success) {
+                    if (DEBUG) Log.v(TAG, "doBackup() success; calling transport");
+                    backupData =
+                        ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
+                    int error = transport.performBackup(packInfo, backupData);
+
+                    // !!! TODO: After successful transport, delete the now-stale data
+                    // and juggle the files so that next time the new state is passed
+                    //backupDataName.delete();
+                    newStateName.renameTo(savedStateName);
+                }
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "Package not found on backup: " + packageName);
+            } catch (FileNotFoundException fnf) {
+                Log.w(TAG, "File not found on backup: ");
+                fnf.printStackTrace();
+            } catch (RemoteException e) {
+                Log.d(TAG, "Remote target " + request.appInfo.packageName + " threw during backup:");
+                e.printStackTrace();
+            } catch (Exception e) {
+                Log.w(TAG, "Final exception guard in backup: ");
+                e.printStackTrace();
+            }
+        }
     }
 
 
@@ -524,12 +579,12 @@
     // ApplicationInfo struct if it is; null if not.
     //
     // !!! TODO: also consider signatures
-    ApplicationInfo isRestorable(PackageInfo packageInfo) {
+    PackageInfo isRestorable(PackageInfo packageInfo) {
         if (packageInfo.packageName != null) {
             try {
-                ApplicationInfo app = mPackageManager.getApplicationInfo(packageInfo.packageName,
+                PackageInfo app = mPackageManager.getPackageInfo(packageInfo.packageName,
                         PackageManager.GET_SIGNATURES);
-                if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
+                if ((app.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
                     return app;
                 }
             } catch (Exception e) {
@@ -541,9 +596,12 @@
 
     class PerformRestoreThread extends Thread {
         private IBackupTransport mTransport;
+        private int mToken;
+        private RestoreSet mImage;
 
-        PerformRestoreThread(IBackupTransport transport) {
+        PerformRestoreThread(IBackupTransport transport, int restoreSetToken) {
             mTransport = transport;
+            mToken = restoreSetToken;
         }
 
         @Override
@@ -556,6 +614,7 @@
              * 3. for each app in the restore set:
              *    3.a. if it's restorable on this device, add it to the restore queue
              * 4. for each app in the restore queue:
+             *    4.a. clear the app data
              *    4.b. get the restore data for the app from the transport
              *    4.c. launch the backup agent for the app
              *    4.d. agent.doRestore() with the data from the server
@@ -576,14 +635,15 @@
                 try {
                     RestoreSet[] images = mTransport.getAvailableRestoreSets();
                     if (images.length > 0) {
-                        // !!! for now we always take the first set
-                        RestoreSet image = images[0];
+                        // !!! TODO: pick out the set for this token
+                        mImage = images[0];
 
                         // build the set of apps we will attempt to restore
-                        PackageInfo[] packages = mTransport.getAppSet(image.token);
-                        HashSet<ApplicationInfo> appsToRestore = new HashSet<ApplicationInfo>();
+                        PackageInfo[] packages = mTransport.getAppSet(mImage.token);
+                        HashSet<PackageInfo> appsToRestore = new HashSet<PackageInfo>();
                         for (PackageInfo pkg: packages) {
-                            ApplicationInfo app = isRestorable(pkg);
+                            // get the real PackageManager idea of the package
+                            PackageInfo app = isRestorable(pkg);
                             if (app != null) {
                                 appsToRestore.add(app);
                             }
@@ -609,19 +669,23 @@
         }
 
         // restore each app in the queue
-        void doQueuedRestores(HashSet<ApplicationInfo> appsToRestore) {
-            for (ApplicationInfo app : appsToRestore) {
+        void doQueuedRestores(HashSet<PackageInfo> appsToRestore) {
+            for (PackageInfo app : appsToRestore) {
                 Log.d(TAG, "starting agent for restore of " + app);
 
-                IBackupAgent agent = null;
                 try {
-                    agent = bindToAgentSynchronous(app, IApplicationThread.BACKUP_MODE_RESTORE);
+                    // Remove the app's data first
+                    clearApplicationDataSynchronous(app.packageName);
+
+                    // Now perform the restore into the clean app
+                    IBackupAgent agent = bindToAgentSynchronous(app.applicationInfo,
+                            IApplicationThread.BACKUP_MODE_RESTORE);
                     if (agent != null) {
                         processOneRestore(app, agent);
                     }
 
                     // unbind even on timeout, just in case
-                    mActivityManager.unbindBackupAgent(app);
+                    mActivityManager.unbindBackupAgent(app.applicationInfo);
                 } catch (SecurityException ex) {
                     // Try for the next one.
                     Log.d(TAG, "error in bind", ex);
@@ -632,9 +696,67 @@
             }
         }
 
-        // do the guts of a restore
-        void processOneRestore(ApplicationInfo app, IBackupAgent agent) {
+        // Do the guts of a restore of one application, derived from the 'mImage'
+        // restore set via the 'mTransport' transport.
+        void processOneRestore(PackageInfo app, IBackupAgent agent) {
             // !!! TODO: actually run the restore through mTransport
+            final String packageName = app.packageName;
+
+            // !!! TODO: get the dirs from the transport
+            File backupDataName = new File(mDataDir, packageName + ".restore");
+            backupDataName.delete();
+            try {
+                ParcelFileDescriptor backupData =
+                    ParcelFileDescriptor.open(backupDataName,
+                            ParcelFileDescriptor.MODE_READ_WRITE |
+                            ParcelFileDescriptor.MODE_CREATE);
+
+                // Run the transport's restore pass
+                // Run the target's backup pass
+                int err = -1;
+                try {
+                    err = mTransport.getRestoreData(mImage.token, app, backupData);
+                } catch (RemoteException e) {
+                    // can't happen
+                } finally {
+                    backupData.close();
+                }
+
+                // Okay, we have the data.  Now have the agent do the restore.
+                File newStateName = new File(mStateDir, packageName + ".new");
+                ParcelFileDescriptor newState =
+                    ParcelFileDescriptor.open(newStateName,
+                            ParcelFileDescriptor.MODE_READ_WRITE |
+                            ParcelFileDescriptor.MODE_CREATE);
+
+                backupData = ParcelFileDescriptor.open(backupDataName,
+                            ParcelFileDescriptor.MODE_READ_ONLY);
+
+                boolean success = false;
+                try {
+                    agent.doRestore(backupData, newState);
+                    success = true;
+                } catch (Exception e) {
+                    Log.e(TAG, "Restore failed for " + packageName);
+                    e.printStackTrace();
+                } finally {
+                    newState.close();
+                    backupData.close();
+                }
+
+                // if everything went okay, remember the recorded state now
+                if (success) {
+                    File savedStateName = new File(mStateDir, packageName);
+                    newStateName.renameTo(savedStateName);
+                }
+            } catch (FileNotFoundException fnfe) {
+                Log.v(TAG, "Couldn't open file for restore: " + fnfe);
+            } catch (IOException ioe) {
+                Log.e(TAG, "Unable to process restore file: " + ioe);
+            } catch (Exception e) {
+                Log.e(TAG, "Final exception guard in restore:");
+                e.printStackTrace();
+            }
         }
     }
 
@@ -761,6 +883,9 @@
 
         // --- Binder interface ---
         public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+            mContext.enforceCallingPermission("android.permission.BACKUP",
+                    "getAvailableRestoreSets");
+
             synchronized(this) {
                 if (mRestoreSets == null) {
                     mRestoreSets = mRestoreTransport.getAvailableRestoreSets();
@@ -770,10 +895,26 @@
         }
 
         public int performRestore(int token) 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);
+                        msg.arg1 = token;
+                        mBackupHandler.sendMessage(msg);
+                        return 0;
+                    }
+                }
+            }
             return -1;
         }
 
         public void endRestoreSession() throws android.os.RemoteException {
+            mContext.enforceCallingPermission("android.permission.BACKUP",
+                    "endRestoreSession");
+
             mRestoreTransport.endSession();
             mRestoreTransport = null;
         }
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index adf07dd..fc37290 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -769,8 +769,8 @@
                 if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
                     if (deadReceivers == null) {
                         deadReceivers = new ArrayList<Receiver>();
-                        deadReceivers.add(record.mReceiver);
                     }
+                    deadReceivers.add(record.mReceiver);
                 }
                 listeners++;
             }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 14dcfd5..6a81178 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -10422,11 +10422,14 @@
             // Not backing this app up any more; reset its OOM adjustment
             updateOomAdjLocked(proc);
 
-            try {
-                proc.thread.scheduleDestroyBackupAgent(appInfo);
-            } catch (Exception e) {
-                Log.e(TAG, "Exception when unbinding backup agent:");
-                e.printStackTrace();
+            // If the app crashed during backup, 'thread' will be null here
+            if (proc.thread != null) {
+                try {
+                    proc.thread.scheduleDestroyBackupAgent(appInfo);
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception when unbinding backup agent:");
+                    e.printStackTrace();
+                }
             }
         }
     }