Merge change 5950 into donut

* changes:
  Fix memory leaks in system_server unlink DeathRecipients in a whole bunch of places to avoid memory leaks
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 44d1eaa..0785029 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -242,8 +242,15 @@
         // Reset any stored values from last time dialog was shown.
         mStoredComponentName = null;
         mStoredAppSearchData = null;
-        
-        return doShow(initialQuery, selectInitialQuery, componentName, appSearchData, globalSearch);
+
+        boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData,
+                globalSearch);
+        if (success) {
+            // Display the drop down as soon as possible instead of waiting for the rest of the
+            // pending UI stuff to get done, so that things appear faster to the user.
+            mSearchAutoComplete.showDropDownAfterLayout();
+        }
+        return success;
     }
     
     /**
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 90dfa0b..8ef1f15 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -67,6 +67,7 @@
     private final int mSearchImeOptions;
     private final boolean mIncludeInGlobalSearch;
     private final boolean mQueryAfterZeroResults;
+    private final String mSettingsDescription;
     private final String mSuggestAuthority;
     private final String mSuggestPath;
     private final String mSuggestSelection;
@@ -134,6 +135,14 @@
     public boolean shouldRewriteQueryFromText() {
         return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_TEXT);
     }
+    
+    /**
+     * Gets the description to use for this source in system search settings, or null if
+     * none has been specified.
+     */
+    public String getSettingsDescription() {
+        return mSettingsDescription;
+    }
 
     /**
      * Retrieve the path for obtaining search suggestions.
@@ -280,6 +289,8 @@
         mQueryAfterZeroResults = a.getBoolean(
                 com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false);
 
+        mSettingsDescription = a.getString(
+                com.android.internal.R.styleable.Searchable_searchSettingsDescription);
         mSuggestAuthority = a.getString(
                 com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
         mSuggestPath = a.getString(
@@ -448,6 +459,7 @@
                         + ",suggestAuthority=" + searchable.getSuggestAuthority()
                         + ",target=" + searchable.getSearchActivity().getClassName()
                         + ",global=" + searchable.shouldIncludeInGlobalSearch()
+                        + ",settingsDescription=" + searchable.getSettingsDescription()
                         + ",threshold=" + searchable.getSuggestThreshold());
             } else {
                 Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
@@ -686,7 +698,8 @@
         mSearchImeOptions = in.readInt();
         mIncludeInGlobalSearch = in.readInt() != 0;
         mQueryAfterZeroResults = in.readInt() != 0;
-
+        
+        mSettingsDescription = in.readString();
         mSuggestAuthority = in.readString();
         mSuggestPath = in.readString();
         mSuggestSelection = in.readString();
@@ -723,6 +736,7 @@
         dest.writeInt(mIncludeInGlobalSearch ? 1 : 0);
         dest.writeInt(mQueryAfterZeroResults ? 1 : 0);
         
+        dest.writeString(mSettingsDescription);
         dest.writeString(mSuggestAuthority);
         dest.writeString(mSuggestPath);
         dest.writeString(mSuggestSelection);
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e84e5b0..2182384 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -124,6 +124,7 @@
     private boolean mBlockCompletion;
 
     private AutoCompleteTextView.ListSelectorHider mHideSelector;
+    private Runnable mShowDropDownRunnable;
 
     private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener;
 
@@ -1080,6 +1081,15 @@
     }
 
     /**
+     * Issues a runnable to show the dropdown as soon as possible.
+     *
+     * @hide internal used only by Search Dialog
+     */
+    public void showDropDownAfterLayout() {
+        post(mShowDropDownRunnable);
+    }
+
+    /**
      * <p>Displays the drop down on screen.</p>
      */
     public void showDropDown() {
@@ -1190,6 +1200,22 @@
 
             mHideSelector = new ListSelectorHider();
 
+            /**
+             * This Runnable exists for the sole purpose of checking if the view layout has got
+             * completed and if so call showDropDown to display the drop down. This is used to show
+             * the drop down as soon as possible after user opens up the search dialog, without
+             * waiting for the normal UI pipeline to do it's job which is slower than this method.
+             */
+            mShowDropDownRunnable = new Runnable() {
+                public void run() {
+                    // View layout should be all done before displaying the drop down.
+                    View view = getDropDownAnchorView();
+                    if (view != null && view.getWindowToken() != null) {
+                        showDropDown();
+                    }
+                }
+            };
+
             mDropDownList = new DropDownListView(context);
             mDropDownList.setSelector(mDropDownListHighlight);
             mDropDownList.setAdapter(mAdapter);
diff --git a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java
index 2ad218f..7dd9d9e 100644
--- a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java
+++ b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java
@@ -78,20 +78,10 @@
 
     /**
      * Sends a command with arguments to the service through the control socket.
-     * Each argument is sent as a C-style zero-terminated string.
      */
     public void sendCommand(String ...args) throws IOException {
         OutputStream out = getControlSocketOutput();
         for (String arg : args) outputString(out, arg);
-        checkSocketResult();
-    }
-
-    /**
-     * Sends a command with arguments to the service through the control socket.
-     */
-    public void sendCommand2(String ...args) throws IOException {
-        OutputStream out = getControlSocketOutput();
-        for (String arg : args) outputString2(out, arg);
         out.write(END_OF_ARGUMENTS);
         out.flush();
         checkSocketResult();
@@ -128,8 +118,9 @@
 
                         if (data == 0) {
                             // re-establish the connection:
-                            // synchronized here so that checkSocketResult() returns
-                            // when new mKeepaliveSocket is available for next cmd
+                            // synchronized here so that checkSocketResult()
+                            // returns when new mKeepaliveSocket is available for
+                            // next cmd
                             synchronized (this) {
                                 setResultAndCloseControlSocket((byte) data);
                                 s = mKeepaliveSocket = createServiceSocket();
@@ -244,12 +235,6 @@
     }
 
     private void outputString(OutputStream out, String s) throws IOException {
-        out.write(s.getBytes());
-        out.write(0);
-        out.flush();
-    }
-
-    private void outputString2(OutputStream out, String s) throws IOException {
         byte[] bytes = s.getBytes();
         out.write(bytes.length);
         out.write(bytes);
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
new file mode 100644
index 0000000..6abf81c
--- /dev/null
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vpn;
+
+import android.net.vpn.L2tpIpsecPskProfile;
+
+import java.io.IOException;
+
+/**
+ * The service that manages the preshared key based L2TP-over-IPSec VPN
+ * connection.
+ */
+class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
+    private static final String IPSEC_DAEMON = "racoon";
+
+    @Override
+    protected void connect(String serverIp, String username, String password)
+            throws IOException {
+        String hostIp = getHostIp();
+        L2tpIpsecPskProfile p = getProfile();
+
+        // IPSEC
+        AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
+        ipsecService.sendCommand(hostIp, serverIp, L2tpService.L2TP_PORT,
+                p.getPresharedKey());
+
+        sleep(2000); // 2 seconds
+
+        // L2TP
+        MtpdHelper.sendCommand(this, L2tpService.L2TP_DAEMON, serverIp,
+                L2tpService.L2TP_PORT,
+                (p.isSecretEnabled() ? p.getSecretString() : null),
+                username, password);
+    }
+}
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
index 877fa6b..bd14110 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
@@ -22,7 +22,7 @@
 import java.io.IOException;
 
 /**
- * The service that manages the L2TP-over-IPSec VPN connection.
+ * The service that manages the certificate based L2TP-over-IPSec VPN connection.
  */
 class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
     private static final String IPSEC_DAEMON = "racoon";
@@ -34,11 +34,10 @@
 
         // IPSEC
         AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
-        ipsecService.sendCommand(
-                String.format("SETKEY %s %s", hostIp, serverIp));
-        ipsecService.sendCommand(String.format("SET_CERTS %s %s %s %s",
-                serverIp, getCaCertPath(), getUserCertPath(),
-                getUserkeyPath()));
+        ipsecService.sendCommand(hostIp, serverIp, L2tpService.L2TP_PORT,
+                getUserkeyPath(), getUserCertPath(), getCaCertPath());
+
+        sleep(2000); // 2 seconds
 
         // L2TP
         L2tpIpsecProfile p = getProfile();
diff --git a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
index 6160900..16d253a 100644
--- a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
+++ b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
@@ -38,7 +38,7 @@
         addPppArguments(vpnService, args, serverIp, username, password);
 
         AndroidServiceProxy mtpd = vpnService.startService(MTPD_SERVICE);
-        mtpd.sendCommand2(args.toArray(new String[args.size()]));
+        mtpd.sendCommand(args.toArray(new String[args.size()]));
     }
 
     private static void addPppArguments(VpnService<?> vpnService,
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 44127ff..6e5d46b 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -24,14 +24,12 @@
 import android.net.vpn.VpnManager;
 import android.net.vpn.VpnProfile;
 import android.net.vpn.VpnState;
-import android.os.FileObserver;
 import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.Log;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
 import java.net.Socket;
 import java.util.ArrayList;
@@ -43,21 +41,18 @@
  */
 abstract class VpnService<E extends VpnProfile> {
     private static final int NOTIFICATION_ID = 1;
-    private static final String PROFILES_ROOT = VpnManager.PROFILES_PATH + "/";
-    public static final String DEFAULT_CONFIG_PATH = "/etc";
 
-    private static final int DNS_TIMEOUT = 3000; // ms
     private static final String DNS1 = "net.dns1";
     private static final String DNS2 = "net.dns2";
+    private static final String VPN_DNS1 = "vpn.dns1";
+    private static final String VPN_DNS2 = "vpn.dns2";
+    private static final String VPN_UP = "vpn.up";
+    private static final String VPN_IS_UP = "1";
+    private static final String VPN_IS_DOWN = "0";
+
     private static final String REMOTE_IP = "net.ipremote";
     private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
-    private static final String SERVER_IP = "net.vpn.server_ip";
 
-    private static final int VPN_TIMEOUT = 30000; // milliseconds
-    private static final int ONE_SECOND = 1000; // milliseconds
-    private static final int FIVE_SECOND = 5000; // milliseconds
-
-    private static final String LOGWRAPPER = "/system/bin/logwrapper";
     private final String TAG = VpnService.class.getSimpleName();
 
     E mProfile;
@@ -76,13 +71,6 @@
 
     private long mStartTime; // VPN connection start time
 
-    // monitors if the VPN connection is sucessfully established
-    private FileMonitor mConnectMonitor;
-
-    // watch dog timer; fired up if the connection cannot be established within
-    // VPN_TIMEOUT
-    private Object mWatchdog;
-
     // for helping managing multiple Android services
     private ServiceHelper mServiceHelper = new ServiceHelper();
 
@@ -110,19 +98,6 @@
         return mServiceHelper.startService(serviceName);
     }
 
-    protected String getPppOptionFilePath() throws IOException {
-        String subpath = getProfileSubpath("/ppp/peers");
-        String[] kids = new File(subpath).list();
-        if ((kids == null) || (kids.length == 0)) {
-            throw new IOException("no option file found in " + subpath);
-        }
-        if (kids.length > 1) {
-            Log.w(TAG, "more than one option file found in " + subpath
-                    + ", arbitrarily choose " + kids[0]);
-        }
-        return subpath + "/" + kids[0];
-    }
-
     /**
      * Returns the VPN profile associated with the connection.
      */
@@ -131,23 +106,6 @@
     }
 
     /**
-     * Returns the profile path where configuration files reside.
-     */
-    protected String getProfilePath() throws IOException {
-        String path = PROFILES_ROOT + mProfile.getId();
-        File dir = new File(path);
-        if (!dir.exists()) throw new IOException("Profile dir does not exist");
-        return path;
-    }
-
-    /**
-     * Returns the path where default configuration files reside.
-     */
-    protected String getDefaultConfigPath() throws IOException {
-        return DEFAULT_CONFIG_PATH;
-    }
-
-    /**
      * Returns the host IP for establishing the VPN connection.
      */
     protected String getHostIp() throws IOException {
@@ -178,14 +136,6 @@
     }
 
     /**
-     * Returns the path of the script file that is executed when the VPN
-     * connection is established.
-     */
-    protected String getConnectMonitorFile() {
-        return "/etc/ppp/ip-up-vpn";
-    }
-
-    /**
      * Sets the system property. The method is blocked until the value is
      * settled in.
      * @param name the name of the property
@@ -222,10 +172,10 @@
         broadcastConnectivity(VpnState.CONNECTING);
 
         String serverIp = getIp(getProfile().getServerName());
-        setSystemProperty(SERVER_IP, serverIp);
-        onBeforeConnect();
 
+        onBeforeConnect();
         connect(serverIp, username, password);
+        waitUntilConnectedOrTimedout();
     }
 
     synchronized void onDisconnect(boolean cleanUpServices) {
@@ -259,39 +209,36 @@
         }
     }
 
-    private void createConnectMonitor() {
-        mConnectMonitor = new FileMonitor(getConnectMonitorFile(),
-                new Runnable() {
-                    public void run() {
-                        onConnectMonitorTriggered();
-                    }
-                });
-    }
-
     private void onBeforeConnect() {
         mNotification.disableNotification();
 
-        createConnectMonitor();
-        mConnectMonitor.startWatching();
-        saveOriginalDnsProperties();
-
-        mWatchdog = startTimer(VPN_TIMEOUT, new Runnable() {
-            public void run() {
-                synchronized (VpnService.this) {
-                    if (mState == VpnState.CONNECTING) {
-                        Log.d(TAG, "       watchdog timer is fired !!");
-                        onError();
-                    }
-                }
-            }
-        });
+        SystemProperties.set(VPN_DNS1, "-");
+        SystemProperties.set(VPN_DNS2, "-");
+        SystemProperties.set(VPN_UP, VPN_IS_DOWN);
+        Log.d(TAG, "       VPN UP: " + SystemProperties.get(VPN_UP));
     }
 
-    private synchronized void onConnectMonitorTriggered() {
-        Log.d(TAG, "onConnectMonitorTriggered()");
+    private void waitUntilConnectedOrTimedout() {
+        sleep(2000); // 2 seconds
+        for (int i = 0; i < 60; i++) {
+            if (VPN_IS_UP.equals(SystemProperties.get(VPN_UP))) {
+                onConnected();
+                return;
+            }
+            sleep(500); // 0.5 second
+        }
 
-        stopTimer(mWatchdog);
-        mConnectMonitor.stopWatching();
+        synchronized (this) {
+            if (mState == VpnState.CONNECTING) {
+                Log.d(TAG, "       connecting timed out !!");
+                onError();
+            }
+        }
+    }
+
+    private synchronized void onConnected() {
+        Log.d(TAG, "onConnected()");
+
         saveVpnDnsProperties();
         saveAndSetDomainSuffices();
         startConnectivityMonitor();
@@ -310,8 +257,6 @@
 
         restoreOriginalDnsProperties();
         restoreOriginalDomainSuffices();
-        if (mConnectMonitor != null) mConnectMonitor.stopWatching();
-        if (mWatchdog != null) stopTimer(mWatchdog);
         mState = VpnState.IDLE;
         broadcastConnectivity(VpnState.IDLE);
 
@@ -345,13 +290,6 @@
         }
     }
 
-    private void saveOriginalDnsProperties() {
-        mOriginalDns1 = SystemProperties.get(DNS1);
-        mOriginalDns2 = SystemProperties.get(DNS2);
-        Log.d(TAG, String.format("save original dns prop: %s, %s",
-                mOriginalDns1, mOriginalDns2));
-    }
-
     private void restoreOriginalDnsProperties() {
         // restore only if they are not overridden
         if (mVpnDns1.equals(SystemProperties.get(DNS1))) {
@@ -365,15 +303,21 @@
     }
 
     private void saveVpnDnsProperties() {
-        mVpnDns1 = mVpnDns2 = "";
+        mOriginalDns1 = mOriginalDns2 = "";
         for (int i = 0; i < 10; i++) {
-            mVpnDns1 = SystemProperties.get(DNS1);
-            mVpnDns2 = SystemProperties.get(DNS2);
-            if (mVpnDns1.equals(mOriginalDns1)) {
+            mVpnDns1 = SystemProperties.get(VPN_DNS1);
+            mVpnDns2 = SystemProperties.get(VPN_DNS2);
+            if (mOriginalDns1.equals(mVpnDns1)) {
                 Log.d(TAG, "wait for vpn dns to settle in..." + i);
                 sleep(500);
             } else {
-                Log.d(TAG, String.format("save vpn dns prop: %s, %s",
+                mOriginalDns1 = SystemProperties.get(DNS1);
+                mOriginalDns2 = SystemProperties.get(DNS2);
+                SystemProperties.set(DNS1, mVpnDns1);
+                SystemProperties.set(DNS2, mVpnDns2);
+                Log.d(TAG, String.format("save original dns prop: %s, %s",
+                        mOriginalDns1, mOriginalDns2));
+                Log.d(TAG, String.format("set vpn dns prop: %s, %s",
                         mVpnDns1, mVpnDns2));
                 return;
             }
@@ -381,23 +325,11 @@
         Log.e(TAG, "saveVpnDnsProperties(): DNS not updated??");
     }
 
-    private void restoreVpnDnsProperties() {
-        if (isNullOrEmpty(mVpnDns1) && isNullOrEmpty(mVpnDns2)) {
-            return;
-        }
-        Log.d(TAG, String.format("restore vpn dns prop: %s --> %s",
-                SystemProperties.get(DNS1), mVpnDns1));
-        Log.d(TAG, String.format("restore vpn dns prop: %s --> %s",
-                SystemProperties.get(DNS2), mVpnDns2));
-        SystemProperties.set(DNS1, mVpnDns1);
-        SystemProperties.set(DNS2, mVpnDns2);
-    }
-
     private void saveAndSetDomainSuffices() {
         mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
         Log.d(TAG, "save original dns search: " + mOriginalDomainSuffices);
         String list = mProfile.getDomainSuffices();
-        if (!isNullOrEmpty(list)) {
+        if (!TextUtils.isEmpty(list)) {
             SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
         }
     }
@@ -423,7 +355,7 @@
                             if (mState != VpnState.CONNECTED) break;
                             mNotification.update();
                             checkConnectivity();
-                            VpnService.this.wait(ONE_SECOND);
+                            VpnService.this.wait(1000); // 1 second
                         }
                     }
                 } catch (InterruptedException e) {
@@ -446,32 +378,6 @@
         }
     }
 
-    private Object startTimer(final int milliseconds, final Runnable task) {
-        Thread thread = new Thread(new Runnable() {
-            public void run() {
-                Log.d(TAG, "watchdog timer started");
-                Thread t = Thread.currentThread();
-                try {
-                    synchronized (t) {
-                        t.wait(milliseconds);
-                    }
-                    task.run();
-                } catch (InterruptedException e) {
-                    // ignored
-                }
-                Log.d(TAG, "watchdog timer stopped");
-            }
-        });
-        thread.start();
-        return thread;
-    }
-
-    private void stopTimer(Object timer) {
-        synchronized (timer) {
-            timer.notify();
-        }
-    }
-
     private String reallyGetHostIp() throws IOException {
         Enumeration<NetworkInterface> ifces =
                 NetworkInterface.getNetworkInterfaces();
@@ -487,33 +393,13 @@
         throw new IOException("Host IP is not available");
     }
 
-    private String getProfileSubpath(String subpath) throws IOException {
-        String path = getProfilePath() + subpath;
-        if (new File(path).exists()) {
-            return path;
-        } else {
-            Log.w(TAG, "Profile subpath does not exist: " + path
-                    + ", use default one");
-            String path2 = getDefaultConfigPath() + subpath;
-            if (!new File(path2).exists()) {
-                throw new IOException("Profile subpath does not exist at "
-                        + path + " or " + path2);
-            }
-            return path2;
-        }
-    }
-
-    private void sleep(int ms) {
+    protected void sleep(int ms) {
         try {
             Thread.currentThread().sleep(ms);
         } catch (InterruptedException e) {
         }
     }
 
-    private static boolean isNullOrEmpty(String message) {
-        return ((message == null) || (message.length() == 0));
-    }
-
     private InetAddress toInetAddress(int addr) throws IOException {
         byte[] aa = new byte[4];
         for (int i= 0; i < aa.length; i++) {
@@ -564,20 +450,6 @@
         }
     }
 
-    private class FileMonitor extends FileObserver {
-        private Runnable mCallback;
-
-        FileMonitor(String path, Runnable callback) {
-            super(path, CLOSE_NOWRITE);
-            mCallback = callback;
-        }
-
-        @Override
-        public void onEvent(int event, String path) {
-            if ((event & CLOSE_NOWRITE) > 0) mCallback.run();
-        }
-    }
-
     // Helper class for showing, updating notification.
     private class NotificationHelper {
         void update() {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index ecdf926..b15c06b 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
 import android.net.Uri;
+import android.provider.Settings;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
@@ -42,7 +43,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -74,12 +74,13 @@
     private static final String TAG = "BackupManagerService";
     private static final boolean DEBUG = true;
 
-    // Persistent properties
-    private static final String BACKUP_TRANSPORT_PROPERTY = "persist.service.bkup.trans";
-    private static final String BACKUP_ENABLED_PROPERTY = "persist.service.bkup.enabled";
+    // Secure settings
+    private static final String BACKUP_TRANSPORT_SETTING = "backup_transport";
+    private static final String BACKUP_ENABLED_SETTING = "backup_enabled";
 
-    // Default time to wait after data changes before we back up the data
-    private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
+    // How often we perform a backup pass.  Privileged external callers can
+    // trigger an immediate pass.
+    private static final long BACKUP_INTERVAL = 60 * 60 * 1000;
 
     private static final int MSG_RUN_BACKUP = 1;
     private static final int MSG_RUN_FULL_BACKUP = 2;
@@ -166,7 +167,8 @@
         // Set up our bookkeeping
         // !!! STOPSHIP: make this disabled by default so that we then gate on
         //               setupwizard or other opt-out UI
-        mEnabled = SystemProperties.getBoolean(BACKUP_ENABLED_PROPERTY, true);
+        mEnabled = (Settings.Secure.getInt(mContext.getContentResolver(),
+                BACKUP_ENABLED_SETTING, 1) != 0);
         mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
         mDataDir = Environment.getDownloadCacheDirectory();
 
@@ -191,8 +193,13 @@
 
         mGoogleTransport = null;
         // !!! TODO: set up the default transport name "the right way"
-        mCurrentTransport = SystemProperties.get(BACKUP_TRANSPORT_PROPERTY,
-                "com.google.android.backup/.BackupTransportService");
+        mCurrentTransport = Settings.Secure.getString(mContext.getContentResolver(),
+                BACKUP_TRANSPORT_SETTING);
+        if (mCurrentTransport == null) {
+            mCurrentTransport = "com.google.android.backup/.BackupTransportService";
+            Settings.Secure.putString(mContext.getContentResolver(),
+                    BACKUP_TRANSPORT_SETTING, mCurrentTransport);
+        }
         if (DEBUG) Log.v(TAG, "Starting with transport " + mCurrentTransport);
 
         // Attach to the Google backup transport.  When this comes up, it will set
@@ -204,7 +211,7 @@
         context.bindService(intent, mGoogleConnection, Context.BIND_AUTO_CREATE);
 
         // Now that we know about valid backup participants, parse any
-        // leftover journal files and schedule a new backup pass
+        // leftover journal files into the pending backup set
         parseLeftoverJournals();
 
         // Register for broadcasts about package install, etc., so we can
@@ -214,7 +221,13 @@
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addDataScheme("package");
         mContext.registerReceiver(mBroadcastReceiver, filter);
-    }
+
+        // Schedule the first backup pass -- okay because no other threads are
+        // running yet
+        if (mEnabled) {
+            scheduleBackupPassLocked(BACKUP_INTERVAL);
+        }
+}
 
     private void makeJournalLocked() {
         try {
@@ -336,35 +349,39 @@
                 ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>();
                 File oldJournal = mJournal;
                 synchronized (mQueueLock) {
-                    if (mPendingBackups.size() == 0) {
-                        Log.v(TAG, "Backup requested but nothing pending");
-                        break;
-                    }
-
-                    for (BackupRequest b: mPendingBackups.values()) {
-                        queue.add(b);
-                    }
-                    Log.v(TAG, "clearing pending backups");
-                    mPendingBackups.clear();
-
-                    // Start a new backup-queue journal file too
-                    if (mJournalStream != null) {
-                        try {
-                            mJournalStream.close();
-                        } catch (IOException e) {
-                            // don't need to do anything
+                    // Do we have any work to do?
+                    if (mPendingBackups.size() > 0) {
+                        for (BackupRequest b: mPendingBackups.values()) {
+                            queue.add(b);
                         }
-                        makeJournalLocked();
-                    }
+                        Log.v(TAG, "clearing pending backups");
+                        mPendingBackups.clear();
 
-                    // At this point, we have started a new journal file, and the old
-                    // file identity is being passed to the backup processing thread.
-                    // When it completes successfully, that old journal file will be
-                    // deleted.  If we crash prior to that, the old journal is parsed
-                    // at next boot and the journaled requests fulfilled.
+                        // Start a new backup-queue journal file too
+                        if (mJournalStream != null) {
+                            try {
+                                mJournalStream.close();
+                            } catch (IOException e) {
+                                // don't need to do anything
+                            }
+                            makeJournalLocked();
+                        }
+
+                        // At this point, we have started a new journal file, and the old
+                        // file identity is being passed to the backup processing thread.
+                        // When it completes successfully, that old journal file will be
+                        // deleted.  If we crash prior to that, the old journal is parsed
+                        // at next boot and the journaled requests fulfilled.
+                        (new PerformBackupThread(transport, queue, oldJournal)).start();
+                    } else {
+                        Log.v(TAG, "Backup requested but nothing pending");
+                    }
                 }
 
-                (new PerformBackupThread(transport, queue, oldJournal)).start();
+                // Schedule the next pass.
+                synchronized (mQueueLock) {
+                    scheduleBackupPassLocked(BACKUP_INTERVAL);
+                }
                 break;
             }
 
@@ -1109,10 +1126,6 @@
                         Log.d(TAG, "    + " + b + " agent=" + b.appInfo.backupAgentName);
                     }
                 }
-                // Schedule a backup pass in a few minutes.  As backup-eligible data
-                // keeps changing, continue to defer the backup pass until things
-                // settle down, to avoid extra overhead.
-                scheduleBackupPassLocked(COLLECTION_INTERVAL);
             }
         } else {
             Log.w(TAG, "dataChanged but no participant pkg " + packageName);
@@ -1148,20 +1161,21 @@
 
         boolean wasEnabled = mEnabled;
         synchronized (this) {
-            SystemProperties.set(BACKUP_ENABLED_PROPERTY, enable ? "true" : "false");
+            Settings.Secure.putInt(mContext.getContentResolver(), BACKUP_ENABLED_SETTING,
+                    enable ? 1 : 0);
             mEnabled = enable;
         }
 
-        if (enable && !wasEnabled) {
-            synchronized (mQueueLock) {
-                if (mPendingBackups.size() > 0) {
-                    // !!! TODO: better policy around timing of the first backup pass
-                    if (DEBUG) Log.v(TAG, "Backup enabled with pending data changes, scheduling");
-                    this.scheduleBackupPassLocked(COLLECTION_INTERVAL);
-                }
+        synchronized (mQueueLock) {
+            if (enable && !wasEnabled) {
+                // if we've just been enabled, start scheduling backup passes
+                scheduleBackupPassLocked(BACKUP_INTERVAL);
+            } else if (!enable) {
+                // No longer enabled, so stop running backups.
+                mBackupHandler.removeMessages(MSG_RUN_BACKUP);
             }
         }
-}
+    }
 
     // Report whether the backup mechanism is currently enabled
     public boolean isBackupEnabled() {
@@ -1206,7 +1220,8 @@
             if (mTransports.get(transport) != null) {
                 prevTransport = mCurrentTransport;
                 mCurrentTransport = transport;
-                SystemProperties.set(BACKUP_TRANSPORT_PROPERTY, transport);
+                Settings.Secure.putString(mContext.getContentResolver(), BACKUP_TRANSPORT_SETTING,
+                        transport);
                 Log.v(TAG, "selectBackupTransport() set " + mCurrentTransport
                         + " returning " + prevTransport);
             } else {
@@ -1334,13 +1349,22 @@
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mQueueLock) {
+            pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled"));
+            boolean scheduled = mBackupHandler.hasMessages(MSG_RUN_BACKUP);
+            if (scheduled != mEnabled) {
+                if (mEnabled) {
+                    pw.println("ERROR: backups enabled but none scheduled!");
+                } else {
+                    pw.println("ERROR: backups are scheduled but not enabled!");
+                }
+            }
             pw.println("Available transports:");
             for (String t : listAllTransports()) {
                 String pad = (t.equals(mCurrentTransport)) ? "  * " : "    ";
                 pw.println(pad + t);
             }
             int N = mBackupParticipants.size();
-            pw.println("Participants:");
+            pw.println("Participants: " + N);
             for (int i=0; i<N; i++) {
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");