/* * Copyright (C) 2020 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 android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Build; import android.os.Build.Partition; import android.os.Environment; import android.os.FileUtils; import android.os.SystemProperties; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.function.Function; /** * Exposes {@link #SYSTEM_PARTITIONS} which represents the partitions in which application packages * can be installed. The partitions are ordered from most generic (lowest priority) to most specific * (greatest priority). * * @hide **/ public class PackagePartitions { public static final int PARTITION_SYSTEM = 0; public static final int PARTITION_VENDOR = 1; public static final int PARTITION_ODM = 2; public static final int PARTITION_OEM = 3; public static final int PARTITION_PRODUCT = 4; public static final int PARTITION_SYSTEM_EXT = 5; @IntDef(prefix = { "PARTITION_" }, value = { PARTITION_SYSTEM, PARTITION_VENDOR, PARTITION_ODM, PARTITION_OEM, PARTITION_PRODUCT, PARTITION_SYSTEM_EXT }) @Retention(RetentionPolicy.SOURCE) public @interface PartitionType {} /** * The list of all system partitions that may contain packages in ascending order of * specificity (the more generic, the earlier in the list a partition appears). */ private static final ArrayList SYSTEM_PARTITIONS = new ArrayList<>(Arrays.asList( new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM, Partition.PARTITION_NAME_SYSTEM, true /* containsPrivApp */, false /* containsOverlay */), new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR, Partition.PARTITION_NAME_VENDOR, true /* containsPrivApp */, true /* containsOverlay */), new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM, Partition.PARTITION_NAME_ODM, true /* containsPrivApp */, true /* containsOverlay */), new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM, Partition.PARTITION_NAME_OEM, false /* containsPrivApp */, true /* containsOverlay */), new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT, Partition.PARTITION_NAME_PRODUCT, true /* containsPrivApp */, true /* containsOverlay */), new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT, Partition.PARTITION_NAME_SYSTEM_EXT, true /* containsPrivApp */, true /* containsOverlay */))); /** * A string to represent the fingerprint of this build and all package partitions. Using it to * determine whether the system update has occurred. Different from {@link Build#FINGERPRINT}, * this string is digested from the fingerprints of the build and all package partitions to * help detect the partition update. */ public static final String FINGERPRINT = getFingerprint(); /** * Returns a list in which the elements are products of the specified function applied to the * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order. */ public static ArrayList getOrderedPartitions( @NonNull Function producer) { final ArrayList out = new ArrayList<>(); for (int i = 0, n = SYSTEM_PARTITIONS.size(); i < n; i++) { final T v = producer.apply(SYSTEM_PARTITIONS.get(i)); if (v != null) { out.add(v); } } return out; } private static File canonicalize(File path) { try { return path.getCanonicalFile(); } catch (IOException e) { return path; } } /** * Returns a fingerprint string for this build and all package partitions. The string is * digested from the fingerprints of the build and all package partitions. * * @return A string to represent the fingerprint of this build and all package partitions. */ @NonNull private static String getFingerprint() { final String[] digestProperties = new String[SYSTEM_PARTITIONS.size() + 1]; for (int i = 0; i < SYSTEM_PARTITIONS.size(); i++) { final String partitionName = SYSTEM_PARTITIONS.get(i).getName(); digestProperties[i] = "ro." + partitionName + ".build.fingerprint"; } digestProperties[SYSTEM_PARTITIONS.size()] = "ro.build.fingerprint"; // build fingerprint return SystemProperties.digestOf(digestProperties); } /** Represents a partition that contains application packages. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public static class SystemPartition { @PartitionType public final int type; @NonNull private final String mName; @NonNull private final DeferredCanonicalFile mFolder; @Nullable private final DeferredCanonicalFile mAppFolder; @Nullable private final DeferredCanonicalFile mPrivAppFolder; @Nullable private final DeferredCanonicalFile mOverlayFolder; @NonNull private final File mNonConicalFolder; private SystemPartition(@NonNull File folder, @PartitionType int type, String name, boolean containsPrivApp, boolean containsOverlay) { this.type = type; this.mName = name; this.mFolder = new DeferredCanonicalFile(folder); this.mAppFolder = new DeferredCanonicalFile(folder, "app"); this.mPrivAppFolder = containsPrivApp ? new DeferredCanonicalFile(folder, "priv-app") : null; this.mOverlayFolder = containsOverlay ? new DeferredCanonicalFile(folder, "overlay") : null; this.mNonConicalFolder = folder; } public SystemPartition(@NonNull SystemPartition original) { this.type = original.type; this.mName = original.mName; this.mFolder = new DeferredCanonicalFile(original.mFolder.getFile()); this.mAppFolder = original.mAppFolder; this.mPrivAppFolder = original.mPrivAppFolder; this.mOverlayFolder = original.mOverlayFolder; this.mNonConicalFolder = original.mNonConicalFolder; } /** * Creates a partition containing the same folders as the original partition but with a * different root folder. */ public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) { this(rootFolder, partition.type, partition.mName, partition.mPrivAppFolder != null, partition.mOverlayFolder != null); } /** * Returns the name identifying the partition. * @see Partition */ @NonNull public String getName() { return mName; } /** Returns the canonical folder of the partition. */ @NonNull public File getFolder() { return mFolder.getFile(); } /** Returns the non-canonical folder of the partition. */ @NonNull public File getNonConicalFolder() { return mNonConicalFolder; } /** Returns the canonical app folder of the partition. */ @Nullable public File getAppFolder() { return mAppFolder == null ? null : mAppFolder.getFile(); } /** Returns the canonical priv-app folder of the partition, if one exists. */ @Nullable public File getPrivAppFolder() { return mPrivAppFolder == null ? null : mPrivAppFolder.getFile(); } /** Returns the canonical overlay folder of the partition, if one exists. */ @Nullable public File getOverlayFolder() { return mOverlayFolder == null ? null : mOverlayFolder.getFile(); } /** Returns whether the partition contains the specified file. */ public boolean containsPath(@NonNull String path) { return containsFile(new File(path)); } /** Returns whether the partition contains the specified file. */ public boolean containsFile(@NonNull File file) { return FileUtils.contains(mFolder.getFile(), canonicalize(file)); } /** Returns whether the partition contains the specified file in its priv-app folder. */ public boolean containsPrivApp(@NonNull File scanFile) { return mPrivAppFolder != null && FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile)); } /** Returns whether the partition contains the specified file in its app folder. */ public boolean containsApp(@NonNull File scanFile) { return mAppFolder != null && FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile)); } /** Returns whether the partition contains the specified file in its overlay folder. */ public boolean containsOverlay(@NonNull File scanFile) { return mOverlayFolder != null && FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile)); } } /** * A class that defers the canonicalization of its underlying file. This must be done so * processes do not attempt to canonicalize files in directories for which the process does not * have the correct selinux policies. */ private static class DeferredCanonicalFile { private boolean mIsCanonical = false; @NonNull private File mFile; private DeferredCanonicalFile(@NonNull File dir) { mFile = dir; } private DeferredCanonicalFile(@NonNull File dir, @NonNull String fileName) { mFile = new File(dir, fileName); } @NonNull private File getFile() { if (!mIsCanonical) { mFile = canonicalize(mFile); mIsCanonical = true; } return mFile; } } }