Handle backup transport registration dynamically

Bug 11369873

Change-Id: I9bbdcc21ce25159c6645690123b5d03c553b0ddc
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 5bfa1b2..1e37fd9 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -23,6 +23,12 @@
 
 /** {@hide} */
 interface IBackupTransport {
+    /**
+     * Ask the transport for the name under which it should be registered.  This will
+     * typically be its host service's component name, but need not be.
+     */
+    String name();
+
 	/**
 	 * Ask the transport for an Intent that can be used to launch any internal
 	 * configuration Activity that it wishes to present.  For example, the transport
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index eb2d1fe..494bc78 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -19,6 +19,7 @@
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.RestoreSet;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -71,6 +72,10 @@
         }
     }
 
+    public String name() {
+        return new ComponentName(mContext, this.getClass()).flattenToShortString();
+    }
+
     public Intent configurationIntent() {
         // The local transport is not user-configurable
         return null;
diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java
new file mode 100644
index 0000000..d05699a
--- /dev/null
+++ b/core/java/com/android/internal/backup/LocalTransportService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.internal.backup;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class LocalTransportService extends Service {
+    private static LocalTransport sTransport = null;
+
+    @Override
+    public void onCreate() {
+        if (sTransport == null) {
+            sTransport = new LocalTransport(this);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sTransport;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bd52f49..6d74ee9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2681,6 +2681,15 @@
         <service android:name="android.hardware.location.GeofenceHardwareService"
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
+
+        <service android:name="com.android.internal.backup.LocalTransportService"
+                android:permission="android.permission.CONFIRM_FULL_BACKUP"
+                android:exported="false">
+            <intent-filter>
+                <action android:name="android.backup.TRANSPORT_HOST" />
+            </intent-filter>
+        </service>
+
     </application>
 
 </manifest>
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a04ee14..baef607 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -46,6 +46,8 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.ContentObserver;
@@ -146,6 +148,7 @@
     static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
 
     static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
+    static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
     // How often we perform a backup pass.  Privileged external callers can
     // trigger an immediate pass.
@@ -251,10 +254,13 @@
     volatile boolean mClearingData;
 
     // Transport bookkeeping
+    final HashMap<String,String> mTransportNames
+            = new HashMap<String,String>();             // component name -> registration name
     final HashMap<String,IBackupTransport> mTransports
-            = new HashMap<String,IBackupTransport>();
+            = new HashMap<String,IBackupTransport>();   // registration name -> binder
+    final ArrayList<TransportConnection> mTransportConnections
+            = new ArrayList<TransportConnection>();
     String mCurrentTransport;
-    IBackupTransport mLocalTransport, mGoogleTransport;
     ActiveRestoreSession mActiveRestoreSession;
 
     // Watch the device provisioning operation during setup
@@ -815,13 +821,7 @@
         }
 
         // Set up our transport options and initialize the default transport
-        // TODO: Have transports register themselves somehow?
         // TODO: Don't create transports that we don't need to?
-        mLocalTransport = new LocalTransport(context);  // This is actually pretty cheap
-        ComponentName localName = new ComponentName(context, LocalTransport.class);
-        registerTransport(localName.flattenToShortString(), mLocalTransport);
-
-        mGoogleTransport = null;
         mCurrentTransport = Settings.Secure.getString(context.getContentResolver(),
                 Settings.Secure.BACKUP_TRANSPORT);
         if ("".equals(mCurrentTransport)) {
@@ -829,28 +829,43 @@
         }
         if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);
 
-        // Attach to the Google backup transport.  When this comes up, it will set
-        // itself as the current transport because we explicitly reset mCurrentTransport
-        // to null.
-        ComponentName transportComponent = new ComponentName("com.google.android.backup",
-                "com.google.android.backup.BackupTransportService");
-        try {
-            // If there's something out there that is supposed to be the Google
-            // backup transport, make sure it's legitimately part of the OS build
-            // and not an app lying about its package name.
-            ApplicationInfo info = mPackageManager.getApplicationInfo(
-                    transportComponent.getPackageName(), 0);
-            if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                if (DEBUG) Slog.v(TAG, "Binding to Google transport");
-                Intent intent = new Intent().setComponent(transportComponent);
-                context.bindServiceAsUser(intent, mGoogleConnection, Context.BIND_AUTO_CREATE,
-                        UserHandle.OWNER);
-            } else {
-                Slog.w(TAG, "Possible Google transport spoof: ignoring " + info);
+        // Find transport hosts and bind to their services
+        Intent transportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
+                transportServiceIntent, 0, UserHandle.USER_OWNER);
+        if (DEBUG) {
+            Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
+        }
+        if (hosts != null) {
+            if (MORE_DEBUG) {
+                for (int i = 0; i < hosts.size(); i++) {
+                    ServiceInfo info = hosts.get(i).serviceInfo;
+                    Slog.v(TAG, "   " + info.packageName + "/" + info.name);
+                }
             }
-        } catch (PackageManager.NameNotFoundException nnf) {
-            // No such package?  No binding.
-            if (DEBUG) Slog.v(TAG, "Google transport not present");
+            for (int i = 0; i < hosts.size(); i++) {
+                try {
+                    ServiceInfo info = hosts.get(i).serviceInfo;
+                    PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
+                    if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+                        ComponentName svcName = new ComponentName(info.packageName, info.name);
+                        if (DEBUG) {
+                            Slog.i(TAG, "Binding to transport host " + svcName);
+                        }
+                        Intent intent = new Intent(transportServiceIntent);
+                        intent.setComponent(svcName);
+                        TransportConnection connection = new TransportConnection();
+                        mTransportConnections.add(connection);
+                        context.bindServiceAsUser(intent,
+                                connection, Context.BIND_AUTO_CREATE,
+                                UserHandle.OWNER);
+                    } else {
+                        Slog.w(TAG, "Transport package not privileged: " + info.packageName);
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Problem resolving transport service: " + e.getMessage());
+                }
+            }
         }
 
         // Now that we know about valid backup participants, parse any
@@ -1298,13 +1313,16 @@
 
     // Add a transport to our set of available backends.  If 'transport' is null, this
     // is an unregistration, and the transport's entry is removed from our bookkeeping.
-    private void registerTransport(String name, IBackupTransport transport) {
+    private void registerTransport(String name, String component, IBackupTransport transport) {
         synchronized (mTransports) {
-            if (DEBUG) Slog.v(TAG, "Registering transport " + name + " = " + transport);
+            if (DEBUG) Slog.v(TAG, "Registering transport "
+                    + component + "::" + name + " = " + transport);
             if (transport != null) {
                 mTransports.put(name, transport);
+                mTransportNames.put(component, name);
             } else {
-                mTransports.remove(name);
+                mTransports.remove(mTransportNames.get(component));
+                mTransportNames.remove(component);
                 // Nothing further to do in the unregistration case
                 return;
             }
@@ -1390,18 +1408,23 @@
         }
     };
 
-    // ----- Track connection to GoogleBackupTransport service -----
-    ServiceConnection mGoogleConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DEBUG) Slog.v(TAG, "Connected to Google transport");
-            mGoogleTransport = IBackupTransport.Stub.asInterface(service);
-            registerTransport(name.flattenToShortString(), mGoogleTransport);
+    // ----- Track connection to transports service -----
+    class TransportConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+            if (DEBUG) Slog.v(TAG, "Connected to transport " + component);
+            try {
+                IBackupTransport transport = IBackupTransport.Stub.asInterface(service);
+                registerTransport(transport.name(), component.flattenToShortString(), transport);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to register transport " + component);
+            }
         }
 
-        public void onServiceDisconnected(ComponentName name) {
-            if (DEBUG) Slog.v(TAG, "Disconnected from Google transport");
-            mGoogleTransport = null;
-            registerTransport(name.flattenToShortString(), null);
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component);
+            registerTransport(null, component.flattenToShortString(), null);
         }
     };
 
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index e075862..e9a9f46 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1269,7 +1269,8 @@
                 frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
             mFrameworkInstallObserver.startWatching();
             scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    | PackageParser.PARSE_IS_SYSTEM_DIR
+                    | PackageParser.PARSE_IS_PRIVILEGED,
                     scanMode | SCAN_NO_DEX, 0);
 
             // Collected privileged system packages.