Home | History | Annotate | Download | only in backup
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.backup;
     18 
     19 import android.app.backup.BackupManager;
     20 import android.app.backup.SelectBackupTransportCallback;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.content.pm.ApplicationInfo;
     26 import android.content.pm.PackageInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.content.pm.ServiceInfo;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.os.RemoteException;
     35 import android.os.UserHandle;
     36 import android.provider.Settings;
     37 import android.util.ArrayMap;
     38 import android.util.ArraySet;
     39 import android.util.EventLog;
     40 import android.util.Log;
     41 import android.util.Slog;
     42 
     43 import com.android.internal.annotations.GuardedBy;
     44 import com.android.internal.backup.IBackupTransport;
     45 import com.android.server.EventLogTags;
     46 
     47 import java.util.ArrayList;
     48 import java.util.Iterator;
     49 import java.util.List;
     50 import java.util.Map;
     51 import java.util.Set;
     52 
     53 /**
     54  * Handles in-memory bookkeeping of all BackupTransport objects.
     55  */
     56 class TransportManager {
     57 
     58     private static final String TAG = "BackupTransportManager";
     59 
     60     private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
     61 
     62     private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
     63     private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
     64     private static final int REBINDING_TIMEOUT_MSG = 1;
     65 
     66     private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
     67     private final Context mContext;
     68     private final PackageManager mPackageManager;
     69     private final Set<ComponentName> mTransportWhitelist;
     70     private final Handler mHandler;
     71 
     72     /**
     73      * This listener is called after we bind to any transport. If it returns true, this is a valid
     74      * transport.
     75      */
     76     private final TransportBoundListener mTransportBoundListener;
     77 
     78     private String mCurrentTransportName;
     79 
     80     /** Lock on this before accessing mValidTransports and mBoundTransports. */
     81     private final Object mTransportLock = new Object();
     82 
     83     /**
     84      * We have detected these transports on the device. Unless in exceptional cases, we are also
     85      * bound to all of these.
     86      */
     87     @GuardedBy("mTransportLock")
     88     private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
     89 
     90     /** We are currently bound to these transports. */
     91     @GuardedBy("mTransportLock")
     92     private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
     93 
     94     TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
     95             TransportBoundListener listener, Looper looper) {
     96         mContext = context;
     97         mPackageManager = context.getPackageManager();
     98         mTransportWhitelist = (whitelist != null) ? whitelist : new ArraySet<>();
     99         mCurrentTransportName = defaultTransport;
    100         mTransportBoundListener = listener;
    101         mHandler = new RebindOnTimeoutHandler(looper);
    102     }
    103 
    104     void onPackageAdded(String packageName) {
    105         // New package added. Bind to all transports it contains.
    106         synchronized (mTransportLock) {
    107             log_verbose("Package added. Binding to all transports. " + packageName);
    108             bindToAllInternal(packageName, null /* all components */);
    109         }
    110     }
    111 
    112     void onPackageRemoved(String packageName) {
    113         // Package removed. Remove all its transports from our list. These transports have already
    114         // been removed from mBoundTransports because onServiceDisconnected would already been
    115         // called on TransportConnection objects.
    116         synchronized (mTransportLock) {
    117             Iterator<Map.Entry<ComponentName, TransportConnection>> iter =
    118                     mValidTransports.entrySet().iterator();
    119             while (iter.hasNext()) {
    120                 Map.Entry<ComponentName, TransportConnection> validTransport = iter.next();
    121                 ComponentName componentName = validTransport.getKey();
    122                 if (componentName.getPackageName().equals(packageName)) {
    123                     TransportConnection transportConnection = validTransport.getValue();
    124                     iter.remove();
    125                     if (transportConnection != null) {
    126                         mContext.unbindService(transportConnection);
    127                         log_verbose("Package removed, removing transport: "
    128                                 + componentName.flattenToShortString());
    129                     }
    130                 }
    131             }
    132         }
    133     }
    134 
    135     void onPackageChanged(String packageName, String[] components) {
    136         synchronized (mTransportLock) {
    137             // Remove all changed components from mValidTransports. We'll bind to them again
    138             // and re-add them if still valid.
    139             for (String component : components) {
    140                 ComponentName componentName = new ComponentName(packageName, component);
    141                 TransportConnection removed = mValidTransports.remove(componentName);
    142                 if (removed != null) {
    143                     mContext.unbindService(removed);
    144                     log_verbose("Package changed. Removing transport: " +
    145                             componentName.flattenToShortString());
    146                 }
    147             }
    148             bindToAllInternal(packageName, components);
    149         }
    150     }
    151 
    152     IBackupTransport getTransportBinder(String transportName) {
    153         synchronized (mTransportLock) {
    154             ComponentName component = mBoundTransports.get(transportName);
    155             if (component == null) {
    156                 Slog.w(TAG, "Transport " + transportName + " not bound.");
    157                 return null;
    158             }
    159             TransportConnection conn = mValidTransports.get(component);
    160             if (conn == null) {
    161                 Slog.w(TAG, "Transport " + transportName + " not valid.");
    162                 return null;
    163             }
    164             return conn.getBinder();
    165         }
    166     }
    167 
    168     IBackupTransport getCurrentTransportBinder() {
    169         return getTransportBinder(mCurrentTransportName);
    170     }
    171 
    172     String getTransportName(IBackupTransport binder) {
    173         synchronized (mTransportLock) {
    174             for (TransportConnection conn : mValidTransports.values()) {
    175                 if (conn.getBinder() == binder) {
    176                     return conn.getName();
    177                 }
    178             }
    179         }
    180         return null;
    181     }
    182 
    183     String[] getBoundTransportNames() {
    184         synchronized (mTransportLock) {
    185             return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
    186         }
    187     }
    188 
    189     ComponentName[] getAllTransportCompenents() {
    190         synchronized (mTransportLock) {
    191             return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]);
    192         }
    193     }
    194 
    195     String getCurrentTransportName() {
    196         return mCurrentTransportName;
    197     }
    198 
    199     Set<ComponentName> getTransportWhitelist() {
    200         return mTransportWhitelist;
    201     }
    202 
    203     String selectTransport(String transport) {
    204         synchronized (mTransportLock) {
    205             String prevTransport = mCurrentTransportName;
    206             mCurrentTransportName = transport;
    207             return prevTransport;
    208         }
    209     }
    210 
    211     void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) {
    212         synchronized (mTransportLock) {
    213             TransportConnection conn = mValidTransports.get(transportComponent);
    214             if (conn == null) {
    215                 listener.onFailure(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
    216                 return;
    217             }
    218             // Transport can be unbound if the process hosting it crashed.
    219             conn.bindIfUnbound();
    220             conn.addListener(listener);
    221         }
    222     }
    223 
    224     void registerAllTransports() {
    225         bindToAllInternal(null /* all packages */, null /* all components */);
    226     }
    227 
    228     /**
    229      * Bind to all transports belonging to the given package and the given component list.
    230      * null acts a wildcard.
    231      *
    232      * If packageName is null, bind to all transports in all packages.
    233      * If components is null, bind to all transports in the given package.
    234      */
    235     private void bindToAllInternal(String packageName, String[] components) {
    236         PackageInfo pkgInfo = null;
    237         if (packageName != null) {
    238             try {
    239                 pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
    240             } catch (PackageManager.NameNotFoundException e) {
    241                 Slog.w(TAG, "Package not found: " + packageName);
    242                 return;
    243             }
    244         }
    245 
    246         Intent intent = new Intent(mTransportServiceIntent);
    247         if (packageName != null) {
    248             intent.setPackage(packageName);
    249         }
    250 
    251         List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
    252                 intent, 0, UserHandle.USER_SYSTEM);
    253         if (hosts != null) {
    254             for (ResolveInfo host : hosts) {
    255                 final ComponentName infoComponentName = host.serviceInfo.getComponentName();
    256                 boolean shouldBind = false;
    257                 if (components != null && packageName != null) {
    258                     for (String component : components) {
    259                         ComponentName cn = new ComponentName(pkgInfo.packageName, component);
    260                         if (infoComponentName.equals(cn)) {
    261                             shouldBind = true;
    262                             break;
    263                         }
    264                     }
    265                 } else {
    266                     shouldBind = true;
    267                 }
    268                 if (shouldBind && isTransportTrusted(infoComponentName)) {
    269                     tryBindTransport(infoComponentName);
    270                 }
    271             }
    272         }
    273     }
    274 
    275     /** Transport has to be whitelisted and privileged. */
    276     private boolean isTransportTrusted(ComponentName transport) {
    277         if (!mTransportWhitelist.contains(transport)) {
    278             Slog.w(TAG, "BackupTransport " + transport.flattenToShortString() +
    279                     " not whitelisted.");
    280             return false;
    281         }
    282         try {
    283             PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
    284             if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
    285                     == 0) {
    286                 Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
    287                 return false;
    288             }
    289         } catch (PackageManager.NameNotFoundException e) {
    290             Slog.w(TAG, "Package not found.", e);
    291             return false;
    292         }
    293         return true;
    294     }
    295 
    296     private void tryBindTransport(ComponentName transportComponentName) {
    297         Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
    298         // TODO: b/22388012 (Multi user backup and restore)
    299         TransportConnection connection = new TransportConnection(transportComponentName);
    300         if (bindToTransport(transportComponentName, connection)) {
    301             synchronized (mTransportLock) {
    302                 mValidTransports.put(transportComponentName, connection);
    303             }
    304         } else {
    305             Slog.w(TAG, "Couldn't bind to transport " + transportComponentName);
    306         }
    307     }
    308 
    309     private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
    310         Intent intent = new Intent(mTransportServiceIntent)
    311                 .setComponent(componentName);
    312         return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
    313                 UserHandle.SYSTEM);
    314     }
    315 
    316     private class TransportConnection implements ServiceConnection {
    317 
    318         // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
    319         private IBackupTransport mBinder;
    320         private final List<SelectBackupTransportCallback> mListeners = new ArrayList<>();
    321         private String mTransportName;
    322 
    323         private final ComponentName mTransportComponent;
    324 
    325         private TransportConnection(ComponentName transportComponent) {
    326             mTransportComponent = transportComponent;
    327         }
    328 
    329         @Override
    330         public void onServiceConnected(ComponentName component, IBinder binder) {
    331             synchronized (mTransportLock) {
    332                 mBinder = IBackupTransport.Stub.asInterface(binder);
    333                 boolean success = false;
    334 
    335                 EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
    336                     component.flattenToShortString(), 1);
    337 
    338                 try {
    339                     mTransportName = mBinder.name();
    340                     // BackupManager requests some fields from the transport. If they are
    341                     // invalid, throw away this transport.
    342                     success = mTransportBoundListener.onTransportBound(mBinder);
    343                 } catch (RemoteException e) {
    344                     success = false;
    345                     Slog.e(TAG, "Couldn't get transport name.", e);
    346                 } finally {
    347                     // we need to intern() the String of the component, so that we can use it with
    348                     // Handler's removeMessages(), which uses == operator to compare the tokens
    349                     String componentShortString = component.flattenToShortString().intern();
    350                     if (success) {
    351                         Slog.d(TAG, "Bound to transport: " + componentShortString);
    352                         mBoundTransports.put(mTransportName, component);
    353                         for (SelectBackupTransportCallback listener : mListeners) {
    354                             listener.onSuccess(mTransportName);
    355                         }
    356                         // cancel rebinding on timeout for this component as we've already connected
    357                         mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
    358                     } else {
    359                         Slog.w(TAG, "Bound to transport " + componentShortString +
    360                                 " but it is invalid");
    361                         EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
    362                                 componentShortString, 0);
    363                         mContext.unbindService(this);
    364                         mValidTransports.remove(component);
    365                         mBinder = null;
    366                         for (SelectBackupTransportCallback listener : mListeners) {
    367                             listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
    368                         }
    369                     }
    370                     mListeners.clear();
    371                 }
    372             }
    373         }
    374 
    375         @Override
    376         public void onServiceDisconnected(ComponentName component) {
    377             synchronized (mTransportLock) {
    378                 mBinder = null;
    379                 mBoundTransports.remove(mTransportName);
    380             }
    381             String componentShortString = component.flattenToShortString();
    382             EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0);
    383             Slog.w(TAG, "Disconnected from transport " + componentShortString);
    384             scheduleRebindTimeout(component);
    385         }
    386 
    387         /**
    388          * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically
    389          * for a few minutes after the binding went away.
    390          */
    391         private void scheduleRebindTimeout(ComponentName component) {
    392             // we need to intern() the String of the component, so that we can use it with Handler's
    393             // removeMessages(), which uses == operator to compare the tokens
    394             final String componentShortString = component.flattenToShortString().intern();
    395             final long rebindTimeout = getRebindTimeout();
    396             mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
    397             Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG);
    398             msg.obj = componentShortString;
    399             mHandler.sendMessageDelayed(msg, rebindTimeout);
    400             Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in "
    401                     + rebindTimeout + "ms");
    402         }
    403 
    404         private IBackupTransport getBinder() {
    405             synchronized (mTransportLock) {
    406                 return mBinder;
    407             }
    408         }
    409 
    410         private String getName() {
    411             synchronized (mTransportLock) {
    412                 return mTransportName;
    413             }
    414         }
    415 
    416         private void bindIfUnbound() {
    417             synchronized (mTransportLock) {
    418                 if (mBinder == null) {
    419                     Slog.d(TAG,
    420                             "Rebinding to transport " + mTransportComponent.flattenToShortString());
    421                     bindToTransport(mTransportComponent, this);
    422                 }
    423             }
    424         }
    425 
    426         private void addListener(SelectBackupTransportCallback listener) {
    427             synchronized (mTransportLock) {
    428                 if (mBinder == null) {
    429                     // We are waiting for bind to complete. If mBinder is set to null after the bind
    430                     // is complete due to transport being invalid, we won't find 'this' connection
    431                     // object in mValidTransports list and this function can't be called.
    432                     mListeners.add(listener);
    433                 } else {
    434                     listener.onSuccess(mTransportName);
    435                 }
    436             }
    437         }
    438 
    439         private long getRebindTimeout() {
    440             final boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
    441                     Settings.Global.DEVICE_PROVISIONED, 0) != 0;
    442             return isDeviceProvisioned
    443                     ? REBINDING_TIMEOUT_PROVISIONED_MS
    444                     : REBINDING_TIMEOUT_UNPROVISIONED_MS;
    445         }
    446     }
    447 
    448     interface TransportBoundListener {
    449         /** Should return true if this is a valid transport. */
    450         boolean onTransportBound(IBackupTransport binder);
    451     }
    452 
    453     private class RebindOnTimeoutHandler extends Handler {
    454 
    455         RebindOnTimeoutHandler(Looper looper) {
    456             super(looper);
    457         }
    458 
    459         @Override
    460         public void handleMessage(Message msg) {
    461             if (msg.what == REBINDING_TIMEOUT_MSG) {
    462                 String componentShortString = (String) msg.obj;
    463                 ComponentName transportComponent =
    464                         ComponentName.unflattenFromString(componentShortString);
    465                 synchronized (mTransportLock) {
    466                     if (mBoundTransports.containsValue(transportComponent)) {
    467                         Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
    468                             + componentShortString + " so not attempting to rebind");
    469                         return;
    470                     }
    471                     Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
    472                             + componentShortString);
    473                     // unbind the existing (broken) connection
    474                     TransportConnection conn = mValidTransports.get(transportComponent);
    475                     if (conn != null) {
    476                         mContext.unbindService(conn);
    477                         Slog.d(TAG, "Unbinding the existing (broken) connection to transport: "
    478                                 + componentShortString);
    479                     }
    480                 }
    481                 // rebind to transport
    482                 tryBindTransport(transportComponent);
    483             } else {
    484                 Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: "
    485                         + msg.what);
    486             }
    487         }
    488     }
    489 
    490     private static void log_verbose(String message) {
    491         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    492             Slog.v(TAG, message);
    493         }
    494     }
    495 }
    496