1 /* 2 * Copyright (C) 2022 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.pm; 18 19 import android.annotation.NonNull; 20 import android.app.usage.UsageEvents; 21 import android.app.usage.UsageStatsManagerInternal; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.IBackgroundInstallControlService; 25 import android.content.pm.InstallSourceInfo; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManagerInternal; 29 import android.content.pm.ParceledListSlice; 30 import android.os.Build; 31 import android.os.Environment; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.os.SystemClock; 36 import android.os.SystemProperties; 37 import android.os.UserHandle; 38 import android.text.TextUtils; 39 import android.util.ArraySet; 40 import android.util.AtomicFile; 41 import android.util.Slog; 42 import android.util.SparseArrayMap; 43 import android.util.SparseSetArray; 44 import android.util.proto.ProtoInputStream; 45 import android.util.proto.ProtoOutputStream; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.server.LocalServices; 49 import com.android.server.ServiceThread; 50 import com.android.server.SystemService; 51 import com.android.server.pm.permission.PermissionManagerServiceInternal; 52 53 import java.io.File; 54 import java.io.FileInputStream; 55 import java.io.FileOutputStream; 56 import java.io.IOException; 57 import java.util.ArrayList; 58 import java.util.List; 59 import java.util.ListIterator; 60 import java.util.Set; 61 import java.util.TreeSet; 62 63 /** 64 * @hide 65 */ 66 public class BackgroundInstallControlService extends SystemService { 67 private static final String TAG = "BackgroundInstallControlService"; 68 69 private static final String DISK_FILE_NAME = "states"; 70 private static final String DISK_DIR_NAME = "bic"; 71 72 private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10; 73 74 private static final int MSG_USAGE_EVENT_RECEIVED = 0; 75 private static final int MSG_PACKAGE_ADDED = 1; 76 private static final int MSG_PACKAGE_REMOVED = 2; 77 78 private final Context mContext; 79 private final BinderService mBinderService; 80 private final PackageManager mPackageManager; 81 private final PackageManagerInternal mPackageManagerInternal; 82 private final UsageStatsManagerInternal mUsageStatsManagerInternal; 83 private final PermissionManagerServiceInternal mPermissionManager; 84 private final Handler mHandler; 85 private final File mDiskFile; 86 87 88 private SparseSetArray<String> mBackgroundInstalledPackages = null; 89 90 // User ID -> package name -> set of foreground time frame 91 private final SparseArrayMap<String, 92 TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames = 93 new SparseArrayMap<>(); 94 BackgroundInstallControlService(@onNull Context context)95 public BackgroundInstallControlService(@NonNull Context context) { 96 this(new InjectorImpl(context)); 97 } 98 99 @VisibleForTesting BackgroundInstallControlService(@onNull Injector injector)100 BackgroundInstallControlService(@NonNull Injector injector) { 101 super(injector.getContext()); 102 mContext = injector.getContext(); 103 mPackageManager = injector.getPackageManager(); 104 mPackageManagerInternal = injector.getPackageManagerInternal(); 105 mPermissionManager = injector.getPermissionManager(); 106 mHandler = new EventHandler(injector.getLooper(), this); 107 mDiskFile = injector.getDiskFile(); 108 mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal(); 109 mUsageStatsManagerInternal.registerListener( 110 (userId, event) -> 111 mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, 112 userId, 113 0, 114 event).sendToTarget() 115 ); 116 mBinderService = new BinderService(this); 117 } 118 119 private static final class BinderService extends IBackgroundInstallControlService.Stub { 120 final BackgroundInstallControlService mService; 121 BinderService(BackgroundInstallControlService service)122 BinderService(BackgroundInstallControlService service) { 123 mService = service; 124 } 125 126 @Override getBackgroundInstalledPackages( @ackageManager.PackageInfoFlagsBits long flags, int userId)127 public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages( 128 @PackageManager.PackageInfoFlagsBits long flags, int userId) { 129 if (!Build.IS_DEBUGGABLE) { 130 return mService.getBackgroundInstalledPackages(flags, userId); 131 } 132 // The debug.transparency.bg-install-apps (only works for debuggable builds) 133 // is used to set mock list of background installed apps for testing. 134 // The list of apps' names is delimited by ",". 135 String propertyString = SystemProperties.get("debug.transparency.bg-install-apps"); 136 if (TextUtils.isEmpty(propertyString)) { 137 return mService.getBackgroundInstalledPackages(flags, userId); 138 } else { 139 return mService.getMockBackgroundInstalledPackages(propertyString); 140 } 141 } 142 } 143 144 @VisibleForTesting getBackgroundInstalledPackages( @ackageManager.PackageInfoFlagsBits long flags, int userId)145 ParceledListSlice<PackageInfo> getBackgroundInstalledPackages( 146 @PackageManager.PackageInfoFlagsBits long flags, int userId) { 147 List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( 148 PackageManager.PackageInfoFlags.of(flags), userId); 149 150 initBackgroundInstalledPackages(); 151 152 ListIterator<PackageInfo> iter = packages.listIterator(); 153 while (iter.hasNext()) { 154 String packageName = iter.next().packageName; 155 if (!mBackgroundInstalledPackages.contains(userId, packageName)) { 156 iter.remove(); 157 } 158 } 159 160 return new ParceledListSlice<>(packages); 161 } 162 163 /** 164 * Mock a list of background installed packages based on the property string. 165 */ 166 @NonNull getMockBackgroundInstalledPackages( @onNull String propertyString)167 ParceledListSlice<PackageInfo> getMockBackgroundInstalledPackages( 168 @NonNull String propertyString) { 169 String[] mockPackageNames = propertyString.split(","); 170 List<PackageInfo> mockPackages = new ArrayList<>(); 171 for (String name : mockPackageNames) { 172 try { 173 PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 174 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL)); 175 mockPackages.add(packageInfo); 176 } catch (PackageManager.NameNotFoundException e) { 177 Slog.w(TAG, "Package's PackageInfo not found " + name); 178 continue; 179 } 180 } 181 return new ParceledListSlice<>(mockPackages); 182 } 183 184 private static class EventHandler extends Handler { 185 private final BackgroundInstallControlService mService; 186 EventHandler(Looper looper, BackgroundInstallControlService service)187 EventHandler(Looper looper, BackgroundInstallControlService service) { 188 super(looper); 189 mService = service; 190 } 191 192 @Override handleMessage(Message msg)193 public void handleMessage(Message msg) { 194 switch (msg.what) { 195 case MSG_USAGE_EVENT_RECEIVED: { 196 mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */); 197 break; 198 } 199 case MSG_PACKAGE_ADDED: { 200 mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */); 201 break; 202 } 203 case MSG_PACKAGE_REMOVED: { 204 mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */); 205 break; 206 } 207 default: 208 Slog.w(TAG, "Unknown message: " + msg.what); 209 } 210 } 211 } 212 handlePackageAdd(String packageName, int userId)213 void handlePackageAdd(String packageName, int userId) { 214 ApplicationInfo appInfo = null; 215 try { 216 appInfo = mPackageManager.getApplicationInfoAsUser(packageName, 217 PackageManager.ApplicationInfoFlags.of(0), userId); 218 } catch (PackageManager.NameNotFoundException e) { 219 Slog.w(TAG, "Package's appInfo not found " + packageName); 220 return; 221 } 222 223 String installerPackageName; 224 String initiatingPackageName; 225 try { 226 final InstallSourceInfo installInfo = mPackageManager.getInstallSourceInfo(packageName); 227 installerPackageName = installInfo.getInstallingPackageName(); 228 initiatingPackageName = installInfo.getInitiatingPackageName(); 229 } catch (PackageManager.NameNotFoundException e) { 230 Slog.w(TAG, "Package's installer not found " + packageName); 231 return; 232 } 233 234 // the installers without INSTALL_PACKAGES perm can't perform 235 // the installation in background. So we can just filter out them. 236 if (mPermissionManager.checkPermission(installerPackageName, 237 android.Manifest.permission.INSTALL_PACKAGES, 238 userId) != PackageManager.PERMISSION_GRANTED) { 239 return; 240 } 241 242 // convert up-time to current time. 243 final long installTimestamp = System.currentTimeMillis() 244 - (SystemClock.uptimeMillis() - appInfo.createTimestamp); 245 246 if (installedByAdb(initiatingPackageName) 247 || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) { 248 return; 249 } 250 251 initBackgroundInstalledPackages(); 252 mBackgroundInstalledPackages.add(userId, packageName); 253 writeBackgroundInstalledPackagesToDisk(); 254 } 255 256 // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be 257 // addressed with b/265203007 installedByAdb(String initiatingPackageName)258 private boolean installedByAdb(String initiatingPackageName) { 259 return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName); 260 } 261 wasForegroundInstallation(String installerPackageName, int userId, long installTimestamp)262 private boolean wasForegroundInstallation(String installerPackageName, 263 int userId, long installTimestamp) { 264 TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames = 265 mInstallerForegroundTimeFrames.get(userId, installerPackageName); 266 267 // The installer never run in foreground. 268 if (foregroundTimeFrames == null) { 269 return false; 270 } 271 272 for (var foregroundTimeFrame : foregroundTimeFrames) { 273 // the foreground time frame starts later than the installation. 274 // so the installation is outside the foreground time frame. 275 if (foregroundTimeFrame.startTimeStampMillis > installTimestamp) { 276 continue; 277 } 278 279 // the foreground time frame is not over yet. 280 // the installation is inside the foreground time frame. 281 if (!foregroundTimeFrame.isDone()) { 282 return true; 283 } 284 285 // the foreground time frame ends later than the installation. 286 // the installation is inside the foreground time frame. 287 if (installTimestamp <= foregroundTimeFrame.endTimeStampMillis) { 288 return true; 289 } 290 } 291 292 // the installation is not inside any of foreground time frames. 293 // so it is not a foreground installation. 294 return false; 295 } 296 handlePackageRemove(String packageName, int userId)297 void handlePackageRemove(String packageName, int userId) { 298 initBackgroundInstalledPackages(); 299 mBackgroundInstalledPackages.remove(userId, packageName); 300 writeBackgroundInstalledPackagesToDisk(); 301 } 302 handleUsageEvent(UsageEvents.Event event, int userId)303 void handleUsageEvent(UsageEvents.Event event, int userId) { 304 if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED 305 && event.mEventType != UsageEvents.Event.ACTIVITY_PAUSED 306 && event.mEventType != UsageEvents.Event.ACTIVITY_STOPPED) { 307 return; 308 } 309 310 if (!isInstaller(event.mPackage, userId)) { 311 return; 312 } 313 314 if (!mInstallerForegroundTimeFrames.contains(userId, event.mPackage)) { 315 mInstallerForegroundTimeFrames.add(userId, event.mPackage, new TreeSet<>()); 316 } 317 318 TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames = 319 mInstallerForegroundTimeFrames.get(userId, event.mPackage); 320 321 if ((foregroundTimeFrames.size() == 0) || foregroundTimeFrames.last().isDone()) { 322 // ignore the other events if there is no open ForegroundTimeFrame. 323 if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED) { 324 return; 325 } 326 foregroundTimeFrames.add(new ForegroundTimeFrame(event.mTimeStamp)); 327 } 328 329 foregroundTimeFrames.last().addEvent(event); 330 331 if (foregroundTimeFrames.size() > MAX_FOREGROUND_TIME_FRAMES_SIZE) { 332 foregroundTimeFrames.pollFirst(); 333 } 334 } 335 336 @VisibleForTesting writeBackgroundInstalledPackagesToDisk()337 void writeBackgroundInstalledPackagesToDisk() { 338 AtomicFile atomicFile = new AtomicFile(mDiskFile); 339 FileOutputStream fileOutputStream; 340 try { 341 fileOutputStream = atomicFile.startWrite(); 342 } catch (IOException e) { 343 Slog.e(TAG, "Failed to start write to states protobuf.", e); 344 return; 345 } 346 347 try { 348 ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); 349 for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) { 350 int userId = mBackgroundInstalledPackages.keyAt(i); 351 for (String packageName : mBackgroundInstalledPackages.get(userId)) { 352 long token = protoOutputStream.start( 353 BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); 354 protoOutputStream.write( 355 BackgroundInstalledPackageProto.PACKAGE_NAME, packageName); 356 protoOutputStream.write( 357 BackgroundInstalledPackageProto.USER_ID, userId + 1); 358 protoOutputStream.end(token); 359 } 360 } 361 protoOutputStream.flush(); 362 atomicFile.finishWrite(fileOutputStream); 363 } catch (Exception e) { 364 Slog.e(TAG, "Failed to finish write to states protobuf.", e); 365 atomicFile.failWrite(fileOutputStream); 366 } 367 } 368 369 @VisibleForTesting initBackgroundInstalledPackages()370 void initBackgroundInstalledPackages() { 371 if (mBackgroundInstalledPackages != null) { 372 return; 373 } 374 375 mBackgroundInstalledPackages = new SparseSetArray<>(); 376 377 if (!mDiskFile.exists()) { 378 return; 379 } 380 381 AtomicFile atomicFile = new AtomicFile(mDiskFile); 382 try (FileInputStream fileInputStream = atomicFile.openRead()) { 383 ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); 384 385 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 386 if (protoInputStream.getFieldNumber() 387 != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { 388 continue; 389 } 390 long token = protoInputStream.start( 391 BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); 392 String packageName = null; 393 int userId = UserHandle.USER_NULL; 394 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 395 switch (protoInputStream.getFieldNumber()) { 396 case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: 397 packageName = protoInputStream.readString( 398 BackgroundInstalledPackageProto.PACKAGE_NAME); 399 break; 400 case (int) BackgroundInstalledPackageProto.USER_ID: 401 userId = protoInputStream.readInt( 402 BackgroundInstalledPackageProto.USER_ID) - 1; 403 break; 404 default: 405 Slog.w(TAG, "Undefined field in proto: " 406 + protoInputStream.getFieldNumber()); 407 } 408 } 409 protoInputStream.end(token); 410 if (packageName != null && userId != UserHandle.USER_NULL) { 411 mBackgroundInstalledPackages.add(userId, packageName); 412 } else { 413 Slog.w(TAG, "Fails to get packageName or UserId from proto file"); 414 } 415 } 416 } catch (IOException e) { 417 Slog.w(TAG, "Error reading state from the disk", e); 418 } 419 } 420 421 @VisibleForTesting getBackgroundInstalledPackages()422 SparseSetArray<String> getBackgroundInstalledPackages() { 423 return mBackgroundInstalledPackages; 424 } 425 426 @VisibleForTesting getInstallerForegroundTimeFrames()427 SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> getInstallerForegroundTimeFrames() { 428 return mInstallerForegroundTimeFrames; 429 } 430 isInstaller(String pkgName, int userId)431 private boolean isInstaller(String pkgName, int userId) { 432 if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) { 433 return true; 434 } 435 return mPermissionManager.checkPermission(pkgName, 436 android.Manifest.permission.INSTALL_PACKAGES, 437 userId) == PackageManager.PERMISSION_GRANTED; 438 } 439 440 @Override onStart()441 public void onStart() { 442 onStart(/* isForTesting= */ false); 443 } 444 445 @VisibleForTesting onStart(boolean isForTesting)446 void onStart(boolean isForTesting) { 447 if (!isForTesting) { 448 publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService); 449 } 450 451 mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() { 452 @Override 453 public void onPackageAdded(String packageName, int uid) { 454 final int userId = UserHandle.getUserId(uid); 455 mHandler.obtainMessage(MSG_PACKAGE_ADDED, 456 userId, 0, packageName).sendToTarget(); 457 } 458 459 @Override 460 public void onPackageRemoved(String packageName, int uid) { 461 final int userId = UserHandle.getUserId(uid); 462 mHandler.obtainMessage(MSG_PACKAGE_REMOVED, 463 userId, 0, packageName).sendToTarget(); 464 } 465 }); 466 } 467 468 // The foreground time frame (ForegroundTimeFrame) represents the period 469 // when a package's activities continuously occupy the foreground. 470 // Each ForegroundTimeFrame starts with an ACTIVITY_RESUMED event, 471 // and then ends with an ACTIVITY_PAUSED or ACTIVITY_STOPPED event. 472 // The startTimeStampMillis stores the timestamp of the ACTIVITY_RESUMED event. 473 // The endTimeStampMillis stores the timestamp of the ACTIVITY_PAUSED or ACTIVITY_STOPPED event 474 // that wraps up the ForegroundTimeFrame. 475 // The activities are designed to handle the edge case in which a package's one activity 476 // seamlessly replace another activity of the same package. Thus, we count these activities 477 // together as a ForegroundTimeFrame. For this scenario, only when all the activities terminate 478 // shall consider the completion of the ForegroundTimeFrame. 479 static final class ForegroundTimeFrame implements Comparable<ForegroundTimeFrame> { 480 public final long startTimeStampMillis; 481 public long endTimeStampMillis; 482 public final Set<Integer> activities; 483 compareTo(ForegroundTimeFrame o)484 public int compareTo(ForegroundTimeFrame o) { 485 int comp = Long.compare(startTimeStampMillis, o.startTimeStampMillis); 486 if (comp != 0) return comp; 487 488 return Integer.compare(hashCode(), o.hashCode()); 489 } 490 ForegroundTimeFrame(long startTimeStampMillis)491 ForegroundTimeFrame(long startTimeStampMillis) { 492 this.startTimeStampMillis = startTimeStampMillis; 493 endTimeStampMillis = 0; 494 activities = new ArraySet<>(); 495 } 496 isDone()497 public boolean isDone() { 498 return endTimeStampMillis != 0; 499 } 500 addEvent(UsageEvents.Event event)501 public void addEvent(UsageEvents.Event event) { 502 switch (event.mEventType) { 503 case UsageEvents.Event.ACTIVITY_RESUMED: 504 activities.add(event.mInstanceId); 505 break; 506 case UsageEvents.Event.ACTIVITY_PAUSED: 507 case UsageEvents.Event.ACTIVITY_STOPPED: 508 if (activities.contains(event.mInstanceId)) { 509 activities.remove(event.mInstanceId); 510 if (activities.size() == 0) { 511 endTimeStampMillis = event.mTimeStamp; 512 } 513 } 514 break; 515 default: 516 } 517 } 518 } 519 520 /** 521 * Dependency injector for {@link #BackgroundInstallControlService)}. 522 */ 523 interface Injector { getContext()524 Context getContext(); 525 getPackageManager()526 PackageManager getPackageManager(); 527 getPackageManagerInternal()528 PackageManagerInternal getPackageManagerInternal(); 529 getUsageStatsManagerInternal()530 UsageStatsManagerInternal getUsageStatsManagerInternal(); 531 getPermissionManager()532 PermissionManagerServiceInternal getPermissionManager(); 533 getLooper()534 Looper getLooper(); 535 getDiskFile()536 File getDiskFile(); 537 } 538 539 private static final class InjectorImpl implements Injector { 540 private final Context mContext; 541 InjectorImpl(Context context)542 InjectorImpl(Context context) { 543 mContext = context; 544 } 545 546 @Override getContext()547 public Context getContext() { 548 return mContext; 549 } 550 551 @Override getPackageManager()552 public PackageManager getPackageManager() { 553 return mContext.getPackageManager(); 554 } 555 556 @Override getPackageManagerInternal()557 public PackageManagerInternal getPackageManagerInternal() { 558 return LocalServices.getService(PackageManagerInternal.class); 559 } 560 561 @Override getUsageStatsManagerInternal()562 public UsageStatsManagerInternal getUsageStatsManagerInternal() { 563 return LocalServices.getService(UsageStatsManagerInternal.class); 564 } 565 566 @Override getPermissionManager()567 public PermissionManagerServiceInternal getPermissionManager() { 568 return LocalServices.getService(PermissionManagerServiceInternal.class); 569 } 570 571 @Override getLooper()572 public Looper getLooper() { 573 ServiceThread serviceThread = new ServiceThread(TAG, 574 android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); 575 serviceThread.start(); 576 return serviceThread.getLooper(); 577 578 } 579 580 @Override getDiskFile()581 public File getDiskFile() { 582 File dir = new File(Environment.getDataSystemDirectory(), DISK_DIR_NAME); 583 File file = new File(dir, DISK_FILE_NAME); 584 return file; 585 } 586 } 587 } 588