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