1 /* 2 * Copyright (C) 2019 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.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.apex.ApexInfo; 23 import android.apex.ApexInfoList; 24 import android.apex.ApexSessionInfo; 25 import android.apex.ApexSessionParams; 26 import android.apex.IApexService; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.ApplicationInfo; 32 import android.content.pm.PackageInfo; 33 import android.content.pm.PackageInstaller; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PackageParser; 36 import android.os.Binder; 37 import android.os.Environment; 38 import android.os.RemoteException; 39 import android.sysprop.ApexProperties; 40 import android.util.Slog; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.os.BackgroundThread; 45 import com.android.internal.util.IndentingPrintWriter; 46 47 import java.io.File; 48 import java.io.PrintWriter; 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.Collections; 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.stream.Collectors; 57 58 /** 59 * ApexManager class handles communications with the apex service to perform operation and queries, 60 * as well as providing caching to avoid unnecessary calls to the service. 61 */ 62 abstract class ApexManager { 63 64 private static final String TAG = "ApexManager"; 65 66 static final int MATCH_ACTIVE_PACKAGE = 1 << 0; 67 static final int MATCH_FACTORY_PACKAGE = 1 << 1; 68 69 /** 70 * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex} 71 * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()} 72 * evaluates to {@code true}. 73 */ create(Context systemContext)74 static ApexManager create(Context systemContext) { 75 if (ApexProperties.updatable().orElse(false)) { 76 return new ApexManagerImpl(systemContext); 77 } else { 78 return new ApexManagerFlattenedApex(); 79 } 80 } 81 82 /** 83 * Minimal information about APEX mount points and the original APEX package they refer to. 84 */ 85 static class ActiveApexInfo { 86 public final File apexDirectory; 87 public final File preinstalledApexPath; 88 ActiveApexInfo(File apexDirectory, File preinstalledApexPath)89 private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) { 90 this.apexDirectory = apexDirectory; 91 this.preinstalledApexPath = preinstalledApexPath; 92 } 93 } 94 95 /** 96 * Returns {@link ActiveApexInfo} records relative to all active APEX packages. 97 */ getActiveApexInfos()98 abstract List<ActiveApexInfo> getActiveApexInfos(); 99 systemReady()100 abstract void systemReady(); 101 102 /** 103 * Retrieves information about an APEX package. 104 * 105 * @param packageName the package name to look for. Note that this is the package name reported 106 * in the APK container manifest (i.e. AndroidManifest.xml), which might 107 * differ from the one reported in the APEX manifest (i.e. 108 * apex_manifest.json). 109 * @param flags the type of package to return. This may match to active packages 110 * and factory (pre-installed) packages. 111 * @return a PackageInfo object with the information about the package, or null if the package 112 * is not found. 113 */ 114 @Nullable getPackageInfo(String packageName, @PackageInfoFlags int flags)115 abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags); 116 117 /** 118 * Retrieves information about all active APEX packages. 119 * 120 * @return a List of PackageInfo object, each one containing information about a different 121 * active package. 122 */ getActivePackages()123 abstract List<PackageInfo> getActivePackages(); 124 125 /** 126 * Retrieves information about all active pre-installed APEX packages. 127 * 128 * @return a List of PackageInfo object, each one containing information about a different 129 * active pre-installed package. 130 */ getFactoryPackages()131 abstract List<PackageInfo> getFactoryPackages(); 132 133 /** 134 * Retrieves information about all inactive APEX packages. 135 * 136 * @return a List of PackageInfo object, each one containing information about a different 137 * inactive package. 138 */ getInactivePackages()139 abstract List<PackageInfo> getInactivePackages(); 140 141 /** 142 * Checks if {@code packageName} is an apex package. 143 * 144 * @param packageName package to check. 145 * @return {@code true} if {@code packageName} is an apex package. 146 */ isApexPackage(String packageName)147 abstract boolean isApexPackage(String packageName); 148 149 /** 150 * Retrieves information about an apexd staged session i.e. the internal state used by apexd to 151 * track the different states of a session. 152 * 153 * @param sessionId the identifier of the session. 154 * @return an ApexSessionInfo object, or null if the session is not known. 155 */ 156 @Nullable getStagedSessionInfo(int sessionId)157 abstract ApexSessionInfo getStagedSessionInfo(int sessionId); 158 159 /** 160 * Submit a staged session to apex service. This causes the apex service to perform some initial 161 * verification and accept or reject the session. Submitting a session successfully is not 162 * enough for it to be activated at the next boot, the caller needs to call 163 * {@link #markStagedSessionReady(int)}. 164 * 165 * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted. 166 * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain 167 * an array of identifiers of all the child sessions. Otherwise it should 168 * be an empty array. 169 * @throws PackageManagerException if call to apexd fails 170 */ submitStagedSession(int sessionId, @NonNull int[] childSessionIds)171 abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds) 172 throws PackageManagerException; 173 174 /** 175 * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be 176 * applied at next reboot. 177 * 178 * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready. 179 * @throws PackageManagerException if call to apexd fails 180 */ markStagedSessionReady(int sessionId)181 abstract void markStagedSessionReady(int sessionId) throws PackageManagerException; 182 183 /** 184 * Marks a staged session as successful. 185 * 186 * <p>Only activated session can be marked as successful. 187 * 188 * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as 189 * successful. 190 */ markStagedSessionSuccessful(int sessionId)191 abstract void markStagedSessionSuccessful(int sessionId); 192 193 /** 194 * Whether the current device supports the management of APEX packages. 195 * 196 * @return true if APEX packages can be managed on this device, false otherwise. 197 */ isApexSupported()198 abstract boolean isApexSupported(); 199 200 /** 201 * Abandons the (only) active session previously submitted. 202 * 203 * @return {@code true} upon success, {@code false} if any remote exception occurs 204 */ revertActiveSessions()205 abstract boolean revertActiveSessions(); 206 207 /** 208 * Abandons the staged session with the given sessionId. 209 * 210 * @return {@code true} upon success, {@code false} if any remote exception occurs 211 */ abortStagedSession(int sessionId)212 abstract boolean abortStagedSession(int sessionId) throws PackageManagerException; 213 214 /** 215 * Uninstalls given {@code apexPackage}. 216 * 217 * <p>NOTE. Device must be rebooted in order for uninstall to take effect. 218 * 219 * @param apexPackagePath package to uninstall. 220 * @return {@code true} upon successful uninstall, {@code false} otherwise. 221 */ uninstallApex(String apexPackagePath)222 abstract boolean uninstallApex(String apexPackagePath); 223 224 /** 225 * Dumps various state information to the provided {@link PrintWriter} object. 226 * 227 * @param pw the {@link PrintWriter} object to send information to. 228 * @param packageName a {@link String} containing a package name, or {@code null}. If set, only 229 * information about that specific package will be dumped. 230 */ dump(PrintWriter pw, @Nullable String packageName)231 abstract void dump(PrintWriter pw, @Nullable String packageName); 232 233 @IntDef( 234 flag = true, 235 prefix = { "MATCH_"}, 236 value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE}) 237 @Retention(RetentionPolicy.SOURCE) 238 @interface PackageInfoFlags{} 239 240 /** 241 * An implementation of {@link ApexManager} that should be used in case device supports updating 242 * APEX packages. 243 */ 244 @VisibleForTesting 245 protected static class ApexManagerImpl extends ApexManager { 246 private final Context mContext; 247 private final Object mLock = new Object(); 248 /** 249 * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code 250 * AndroidManifest.xml} 251 * 252 * <p>Note that key of this map is {@code packageName} field of the corresponding {@code 253 * AndroidManifest.xml}. 254 */ 255 @GuardedBy("mLock") 256 private List<PackageInfo> mAllPackagesCache; 257 ApexManagerImpl(Context context)258 ApexManagerImpl(Context context) { 259 mContext = context; 260 } 261 262 /** 263 * Whether an APEX package is active or not. 264 * 265 * @param packageInfo the package to check 266 * @return {@code true} if this package is active, {@code false} otherwise. 267 */ isActive(PackageInfo packageInfo)268 private static boolean isActive(PackageInfo packageInfo) { 269 return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 270 } 271 272 /** 273 * Whether the APEX package is pre-installed or not. 274 * 275 * @param packageInfo the package to check 276 * @return {@code true} if this package is pre-installed, {@code false} otherwise. 277 */ isFactory(PackageInfo packageInfo)278 private static boolean isFactory(PackageInfo packageInfo) { 279 return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 280 } 281 282 /** 283 * Retrieve the service from ServiceManager. If the service is not running, it will be 284 * started, and this function will block until it is ready. 285 */ 286 @VisibleForTesting waitForApexService()287 protected IApexService waitForApexService() { 288 try { 289 return IApexService.Stub.asInterface(Binder.waitForService("apexservice")); 290 } catch (RemoteException e) { 291 throw new IllegalStateException("Required service apexservice not available"); 292 } 293 } 294 295 @Override getActiveApexInfos()296 List<ActiveApexInfo> getActiveApexInfos() { 297 try { 298 return Arrays.stream(waitForApexService().getActivePackages()) 299 .map(apexInfo -> new ActiveApexInfo( 300 new File( 301 Environment.getApexDirectory() + File.separator 302 + apexInfo.moduleName), 303 new File(apexInfo.modulePath))).collect( 304 Collectors.toList()); 305 } catch (RemoteException e) { 306 Slog.e(TAG, "Unable to retrieve packages from apexservice", e); 307 } 308 return Collections.emptyList(); 309 } 310 311 @Override systemReady()312 void systemReady() { 313 mContext.registerReceiver(new BroadcastReceiver() { 314 @Override 315 public void onReceive(Context context, Intent intent) { 316 // Post populateAllPackagesCacheIfNeeded to a background thread, since it's 317 // expensive to run it in broadcast handler thread. 318 BackgroundThread.getHandler().post(() -> populateAllPackagesCacheIfNeeded()); 319 mContext.unregisterReceiver(this); 320 } 321 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); 322 } 323 populateAllPackagesCacheIfNeeded()324 private void populateAllPackagesCacheIfNeeded() { 325 synchronized (mLock) { 326 if (mAllPackagesCache != null) { 327 return; 328 } 329 try { 330 mAllPackagesCache = new ArrayList<>(); 331 HashSet<String> activePackagesSet = new HashSet<>(); 332 HashSet<String> factoryPackagesSet = new HashSet<>(); 333 final ApexInfo[] allPkgs = waitForApexService().getAllPackages(); 334 for (ApexInfo ai : allPkgs) { 335 // If the device is using flattened APEX, don't report any APEX 336 // packages since they won't be managed or updated by PackageManager. 337 if ((new File(ai.modulePath)).isDirectory()) { 338 break; 339 } 340 int flags = PackageManager.GET_META_DATA 341 | PackageManager.GET_SIGNING_CERTIFICATES 342 | PackageManager.GET_SIGNATURES; 343 PackageParser.Package pkg; 344 try { 345 File apexFile = new File(ai.modulePath); 346 PackageParser pp = new PackageParser(); 347 pkg = pp.parsePackage(apexFile, flags, false); 348 PackageParser.collectCertificates(pkg, false); 349 } catch (PackageParser.PackageParserException pe) { 350 throw new IllegalStateException("Unable to parse: " + ai, pe); 351 } 352 353 final PackageInfo packageInfo = 354 PackageParser.generatePackageInfo(pkg, ai, flags); 355 mAllPackagesCache.add(packageInfo); 356 if (ai.isActive) { 357 if (activePackagesSet.contains(packageInfo.packageName)) { 358 throw new IllegalStateException( 359 "Two active packages have the same name: " 360 + packageInfo.packageName); 361 } 362 activePackagesSet.add(packageInfo.packageName); 363 } 364 if (ai.isFactory) { 365 if (factoryPackagesSet.contains(packageInfo.packageName)) { 366 throw new IllegalStateException( 367 "Two factory packages have the same name: " 368 + packageInfo.packageName); 369 } 370 factoryPackagesSet.add(packageInfo.packageName); 371 } 372 373 } 374 } catch (RemoteException re) { 375 Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); 376 throw new RuntimeException(re); 377 } 378 } 379 } 380 381 @Override getPackageInfo(String packageName, @PackageInfoFlags int flags)382 @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) { 383 populateAllPackagesCacheIfNeeded(); 384 boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0; 385 boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0; 386 for (PackageInfo packageInfo: mAllPackagesCache) { 387 if (!packageInfo.packageName.equals(packageName)) { 388 continue; 389 } 390 if ((matchActive && isActive(packageInfo)) 391 || (matchFactory && isFactory(packageInfo))) { 392 return packageInfo; 393 } 394 } 395 return null; 396 } 397 398 @Override getActivePackages()399 List<PackageInfo> getActivePackages() { 400 populateAllPackagesCacheIfNeeded(); 401 return mAllPackagesCache 402 .stream() 403 .filter(item -> isActive(item)) 404 .collect(Collectors.toList()); 405 } 406 407 @Override getFactoryPackages()408 List<PackageInfo> getFactoryPackages() { 409 populateAllPackagesCacheIfNeeded(); 410 return mAllPackagesCache 411 .stream() 412 .filter(item -> isFactory(item)) 413 .collect(Collectors.toList()); 414 } 415 416 @Override getInactivePackages()417 List<PackageInfo> getInactivePackages() { 418 populateAllPackagesCacheIfNeeded(); 419 return mAllPackagesCache 420 .stream() 421 .filter(item -> !isActive(item)) 422 .collect(Collectors.toList()); 423 } 424 425 @Override isApexPackage(String packageName)426 boolean isApexPackage(String packageName) { 427 if (!isApexSupported()) return false; 428 populateAllPackagesCacheIfNeeded(); 429 for (PackageInfo packageInfo : mAllPackagesCache) { 430 if (packageInfo.packageName.equals(packageName)) { 431 return true; 432 } 433 } 434 return false; 435 } 436 437 @Override getStagedSessionInfo(int sessionId)438 @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) { 439 try { 440 ApexSessionInfo apexSessionInfo = 441 waitForApexService().getStagedSessionInfo(sessionId); 442 if (apexSessionInfo.isUnknown) { 443 return null; 444 } 445 return apexSessionInfo; 446 } catch (RemoteException re) { 447 Slog.e(TAG, "Unable to contact apexservice", re); 448 throw new RuntimeException(re); 449 } 450 } 451 452 @Override submitStagedSession(int sessionId, @NonNull int[] childSessionIds)453 ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds) 454 throws PackageManagerException { 455 try { 456 final ApexInfoList apexInfoList = new ApexInfoList(); 457 ApexSessionParams params = new ApexSessionParams(); 458 params.sessionId = sessionId; 459 params.childSessionIds = childSessionIds; 460 waitForApexService().submitStagedSession(params, apexInfoList); 461 return apexInfoList; 462 } catch (RemoteException re) { 463 Slog.e(TAG, "Unable to contact apexservice", re); 464 throw new RuntimeException(re); 465 } catch (Exception e) { 466 throw new PackageManagerException( 467 PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 468 "apexd verification failed : " + e.getMessage()); 469 } 470 } 471 472 @Override markStagedSessionReady(int sessionId)473 void markStagedSessionReady(int sessionId) throws PackageManagerException { 474 try { 475 waitForApexService().markStagedSessionReady(sessionId); 476 } catch (RemoteException re) { 477 Slog.e(TAG, "Unable to contact apexservice", re); 478 throw new RuntimeException(re); 479 } catch (Exception e) { 480 throw new PackageManagerException( 481 PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 482 "Failed to mark apexd session as ready : " + e.getMessage()); 483 } 484 } 485 486 @Override markStagedSessionSuccessful(int sessionId)487 void markStagedSessionSuccessful(int sessionId) { 488 try { 489 waitForApexService().markStagedSessionSuccessful(sessionId); 490 } catch (RemoteException re) { 491 Slog.e(TAG, "Unable to contact apexservice", re); 492 throw new RuntimeException(re); 493 } catch (Exception e) { 494 // It is fine to just log an exception in this case. APEXd will be able to recover 495 // in case markStagedSessionSuccessful fails. 496 Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e); 497 } 498 } 499 500 @Override isApexSupported()501 boolean isApexSupported() { 502 return true; 503 } 504 505 @Override revertActiveSessions()506 boolean revertActiveSessions() { 507 try { 508 waitForApexService().revertActiveSessions(); 509 return true; 510 } catch (RemoteException re) { 511 Slog.e(TAG, "Unable to contact apexservice", re); 512 return false; 513 } catch (Exception e) { 514 Slog.e(TAG, e.getMessage(), e); 515 return false; 516 } 517 } 518 519 @Override abortStagedSession(int sessionId)520 boolean abortStagedSession(int sessionId) throws PackageManagerException { 521 try { 522 waitForApexService().abortStagedSession(sessionId); 523 return true; 524 } catch (RemoteException re) { 525 Slog.e(TAG, "Unable to contact apexservice", re); 526 return false; 527 } catch (Exception e) { 528 throw new PackageManagerException( 529 PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 530 "Failed to abort staged session : " + e.getMessage()); 531 } 532 } 533 534 @Override uninstallApex(String apexPackagePath)535 boolean uninstallApex(String apexPackagePath) { 536 try { 537 waitForApexService().unstagePackages(Collections.singletonList(apexPackagePath)); 538 return true; 539 } catch (Exception e) { 540 return false; 541 } 542 } 543 544 /** 545 * Dump information about the packages contained in a particular cache 546 * @param packagesCache the cache to print information about. 547 * @param packageName a {@link String} containing a package name, or {@code null}. If set, 548 * only information about that specific package will be dumped. 549 * @param ipw the {@link IndentingPrintWriter} object to send information to. 550 */ dumpFromPackagesCache( List<PackageInfo> packagesCache, @Nullable String packageName, IndentingPrintWriter ipw)551 void dumpFromPackagesCache( 552 List<PackageInfo> packagesCache, 553 @Nullable String packageName, 554 IndentingPrintWriter ipw) { 555 ipw.println(); 556 ipw.increaseIndent(); 557 for (PackageInfo pi : packagesCache) { 558 if (packageName != null && !packageName.equals(pi.packageName)) { 559 continue; 560 } 561 ipw.println(pi.packageName); 562 ipw.increaseIndent(); 563 ipw.println("Version: " + pi.versionCode); 564 ipw.println("Path: " + pi.applicationInfo.sourceDir); 565 ipw.println("IsActive: " + isActive(pi)); 566 ipw.println("IsFactory: " + isFactory(pi)); 567 ipw.decreaseIndent(); 568 } 569 ipw.decreaseIndent(); 570 ipw.println(); 571 } 572 573 @Override dump(PrintWriter pw, @Nullable String packageName)574 void dump(PrintWriter pw, @Nullable String packageName) { 575 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); 576 try { 577 populateAllPackagesCacheIfNeeded(); 578 ipw.println(); 579 ipw.println("Active APEX packages:"); 580 dumpFromPackagesCache(getActivePackages(), packageName, ipw); 581 ipw.println("Inactive APEX packages:"); 582 dumpFromPackagesCache(getInactivePackages(), packageName, ipw); 583 ipw.println("Factory APEX packages:"); 584 dumpFromPackagesCache(getFactoryPackages(), packageName, ipw); 585 ipw.increaseIndent(); 586 ipw.println("APEX session state:"); 587 ipw.increaseIndent(); 588 final ApexSessionInfo[] sessions = waitForApexService().getSessions(); 589 for (ApexSessionInfo si : sessions) { 590 ipw.println("Session ID: " + si.sessionId); 591 ipw.increaseIndent(); 592 if (si.isUnknown) { 593 ipw.println("State: UNKNOWN"); 594 } else if (si.isVerified) { 595 ipw.println("State: VERIFIED"); 596 } else if (si.isStaged) { 597 ipw.println("State: STAGED"); 598 } else if (si.isActivated) { 599 ipw.println("State: ACTIVATED"); 600 } else if (si.isActivationFailed) { 601 ipw.println("State: ACTIVATION FAILED"); 602 } else if (si.isSuccess) { 603 ipw.println("State: SUCCESS"); 604 } else if (si.isRevertInProgress) { 605 ipw.println("State: REVERT IN PROGRESS"); 606 } else if (si.isReverted) { 607 ipw.println("State: REVERTED"); 608 } else if (si.isRevertFailed) { 609 ipw.println("State: REVERT FAILED"); 610 } 611 ipw.decreaseIndent(); 612 } 613 ipw.decreaseIndent(); 614 } catch (RemoteException e) { 615 ipw.println("Couldn't communicate with apexd."); 616 } 617 } 618 } 619 620 /** 621 * An implementation of {@link ApexManager} that should be used in case device does not support 622 * updating APEX packages. 623 */ 624 private static final class ApexManagerFlattenedApex extends ApexManager { 625 626 @Override getActiveApexInfos()627 List<ActiveApexInfo> getActiveApexInfos() { 628 // There is no apexd running in case of flattened apex 629 // We look up the /apex directory and identify the active APEX modules from there. 630 // As "preinstalled" path, we just report /system since in the case of flattened APEX 631 // the /apex directory is just a symlink to /system/apex. 632 List<ActiveApexInfo> result = new ArrayList<>(); 633 File apexDir = Environment.getApexDirectory(); 634 // In flattened configuration, init special-case the art directory and bind-mounts 635 // com.android.art.{release|debug} to com.android.art. At the time of writing, these 636 // directories are copied from the kArtApexDirNames variable in 637 // system/core/init/mount_namespace.cpp. 638 String[] skipDirs = {"com.android.art.release", "com.android.art.debug"}; 639 if (apexDir.isDirectory()) { 640 File[] files = apexDir.listFiles(); 641 // listFiles might be null if system server doesn't have permission to read 642 // a directory. 643 if (files != null) { 644 for (File file : files) { 645 if (file.isDirectory() && !file.getName().contains("@")) { 646 for (String skipDir : skipDirs) { 647 if (file.getName().equals(skipDir)) { 648 continue; 649 } 650 } 651 result.add(new ActiveApexInfo(file, Environment.getRootDirectory())); 652 } 653 } 654 } 655 } 656 return result; 657 } 658 659 @Override systemReady()660 void systemReady() { 661 // No-op 662 } 663 664 @Override getPackageInfo(String packageName, int flags)665 PackageInfo getPackageInfo(String packageName, int flags) { 666 return null; 667 } 668 669 @Override getActivePackages()670 List<PackageInfo> getActivePackages() { 671 return Collections.emptyList(); 672 } 673 674 @Override getFactoryPackages()675 List<PackageInfo> getFactoryPackages() { 676 return Collections.emptyList(); 677 } 678 679 @Override getInactivePackages()680 List<PackageInfo> getInactivePackages() { 681 return Collections.emptyList(); 682 } 683 684 @Override isApexPackage(String packageName)685 boolean isApexPackage(String packageName) { 686 return false; 687 } 688 689 @Override getStagedSessionInfo(int sessionId)690 ApexSessionInfo getStagedSessionInfo(int sessionId) { 691 throw new UnsupportedOperationException(); 692 } 693 694 @Override submitStagedSession(int sessionId, int[] childSessionIds)695 ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds) 696 throws PackageManagerException { 697 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, 698 "Device doesn't support updating APEX"); 699 } 700 701 @Override markStagedSessionReady(int sessionId)702 void markStagedSessionReady(int sessionId) { 703 throw new UnsupportedOperationException(); 704 } 705 706 @Override markStagedSessionSuccessful(int sessionId)707 void markStagedSessionSuccessful(int sessionId) { 708 throw new UnsupportedOperationException(); 709 } 710 711 @Override isApexSupported()712 boolean isApexSupported() { 713 return false; 714 } 715 716 @Override revertActiveSessions()717 boolean revertActiveSessions() { 718 throw new UnsupportedOperationException(); 719 } 720 721 @Override abortStagedSession(int sessionId)722 boolean abortStagedSession(int sessionId) throws PackageManagerException { 723 throw new UnsupportedOperationException(); 724 } 725 726 @Override uninstallApex(String apexPackagePath)727 boolean uninstallApex(String apexPackagePath) { 728 throw new UnsupportedOperationException(); 729 } 730 731 @Override dump(PrintWriter pw, String packageName)732 void dump(PrintWriter pw, String packageName) { 733 // No-op 734 } 735 } 736 } 737