1 /* 2 * Copyright (C) 2016 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 package android.content.pm.split; 17 18 import android.annotation.IntRange; 19 import android.annotation.NonNull; 20 import android.content.pm.PackageParser; 21 import android.util.IntArray; 22 import android.util.SparseArray; 23 24 import libcore.util.EmptyArray; 25 26 import java.util.Arrays; 27 import java.util.BitSet; 28 29 /** 30 * A helper class that implements the dependency tree traversal for splits. Callbacks 31 * are implemented by subclasses to notify whether a split has already been constructed 32 * and is cached, and to actually create the split requested. 33 * 34 * This helper is meant to be subclassed so as to reduce the number of allocations 35 * needed to make use of it. 36 * 37 * All inputs and outputs are assumed to be indices into an array of splits. 38 * 39 * @hide 40 */ 41 public abstract class SplitDependencyLoader<E extends Exception> { 42 private final @NonNull SparseArray<int[]> mDependencies; 43 44 /** 45 * Construct a new SplitDependencyLoader. Meant to be called from the 46 * subclass constructor. 47 * @param dependencies The dependency tree of splits. 48 */ SplitDependencyLoader(@onNull SparseArray<int[]> dependencies)49 protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) { 50 mDependencies = dependencies; 51 } 52 53 /** 54 * Traverses the dependency tree and constructs any splits that are not already 55 * cached. This routine short-circuits and skips the creation of splits closer to the 56 * root if they are cached, as reported by the subclass implementation of 57 * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass 58 * implementation of {@link #constructSplit(int, int[], int)}. 59 * @param splitIdx The index of the split to load. 0 represents the base Application. 60 */ loadDependenciesForSplit(@ntRangefrom = 0) int splitIdx)61 protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E { 62 // Quick check before any allocations are done. 63 if (isSplitCached(splitIdx)) { 64 return; 65 } 66 67 // Special case the base, since it has no dependencies. 68 if (splitIdx == 0) { 69 final int[] configSplitIndices = collectConfigSplitIndices(0); 70 constructSplit(0, configSplitIndices, -1); 71 return; 72 } 73 74 // Build up the dependency hierarchy. 75 final IntArray linearDependencies = new IntArray(); 76 linearDependencies.add(splitIdx); 77 78 // Collect all the dependencies that need to be constructed. 79 // They will be listed from leaf to root. 80 while (true) { 81 // Only follow the first index into the array. The others are config splits and 82 // get loaded with the split. 83 final int[] deps = mDependencies.get(splitIdx); 84 if (deps != null && deps.length > 0) { 85 splitIdx = deps[0]; 86 } else { 87 splitIdx = -1; 88 } 89 90 if (splitIdx < 0 || isSplitCached(splitIdx)) { 91 break; 92 } 93 94 linearDependencies.add(splitIdx); 95 } 96 97 // Visit each index, from right to left (root to leaf). 98 int parentIdx = splitIdx; 99 for (int i = linearDependencies.size() - 1; i >= 0; i--) { 100 final int idx = linearDependencies.get(i); 101 final int[] configSplitIndices = collectConfigSplitIndices(idx); 102 constructSplit(idx, configSplitIndices, parentIdx); 103 parentIdx = idx; 104 } 105 } 106 collectConfigSplitIndices(int splitIdx)107 private @NonNull int[] collectConfigSplitIndices(int splitIdx) { 108 // The config splits appear after the first element. 109 final int[] deps = mDependencies.get(splitIdx); 110 if (deps == null || deps.length <= 1) { 111 return EmptyArray.INT; 112 } 113 return Arrays.copyOfRange(deps, 1, deps.length); 114 } 115 116 /** 117 * Subclass to report whether the split at `splitIdx` is cached and need not be constructed. 118 * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached. 119 * @param splitIdx The index of the split to check for in the cache. 120 * @return true if the split is cached and does not need to be constructed. 121 */ isSplitCached(@ntRangefrom = 0) int splitIdx)122 protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx); 123 124 /** 125 * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`. 126 * The result is expected to be cached by the subclass in its own structures. 127 * @param splitIdx The index of the split to construct. 0 represents the base Application. 128 * @param configSplitIndices The array of configuration splits to load along with this split. 129 * May be empty (length == 0) but never null. 130 * @param parentSplitIdx The index of the parent split. -1 if there is no parent. 131 * @throws E Subclass defined exception representing failure to construct a split. 132 */ constructSplit(@ntRangefrom = 0) int splitIdx, @NonNull @IntRange(from = 1) int[] configSplitIndices, @IntRange(from = -1) int parentSplitIdx)133 protected abstract void constructSplit(@IntRange(from = 0) int splitIdx, 134 @NonNull @IntRange(from = 1) int[] configSplitIndices, 135 @IntRange(from = -1) int parentSplitIdx) throws E; 136 137 public static class IllegalDependencyException extends Exception { IllegalDependencyException(String message)138 private IllegalDependencyException(String message) { 139 super(message); 140 } 141 } 142 append(int[] src, int elem)143 private static int[] append(int[] src, int elem) { 144 if (src == null) { 145 return new int[] { elem }; 146 } 147 int[] dst = Arrays.copyOf(src, src.length + 1); 148 dst[src.length] = elem; 149 return dst; 150 } 151 createDependenciesFromPackage( PackageParser.PackageLite pkg)152 public static @NonNull SparseArray<int[]> createDependenciesFromPackage( 153 PackageParser.PackageLite pkg) throws IllegalDependencyException { 154 // The data structure that holds the dependencies. In PackageParser, splits are stored 155 // in their own array, separate from the base. We treat all paths as equals, so 156 // we need to insert the base as index 0, and shift all other splits. 157 final SparseArray<int[]> splitDependencies = new SparseArray<>(); 158 159 // The base depends on nothing. 160 splitDependencies.put(0, new int[] {-1}); 161 162 // First write out the <uses-split> dependencies. These must appear first in the 163 // array of ints, as is convention in this class. 164 for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) { 165 if (!pkg.isFeatureSplits[splitIdx]) { 166 // Non-feature splits don't have dependencies. 167 continue; 168 } 169 170 // Implicit dependency on the base. 171 final int targetIdx; 172 final String splitDependency = pkg.usesSplitNames[splitIdx]; 173 if (splitDependency != null) { 174 final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency); 175 if (depIdx < 0) { 176 throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx] 177 + "' requires split '" + splitDependency + "', which is missing."); 178 } 179 targetIdx = depIdx + 1; 180 } else { 181 // Implicitly depend on the base. 182 targetIdx = 0; 183 } 184 splitDependencies.put(splitIdx + 1, new int[] {targetIdx}); 185 } 186 187 // Write out the configForSplit reverse-dependencies. These appear after the <uses-split> 188 // dependencies and are considered leaves. 189 // 190 // At this point, all splits in splitDependencies have the first element in their array set. 191 for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) { 192 if (pkg.isFeatureSplits[splitIdx]) { 193 // Feature splits are not configForSplits. 194 continue; 195 } 196 197 // Implicit feature for the base. 198 final int targetSplitIdx; 199 final String configForSplit = pkg.configForSplit[splitIdx]; 200 if (configForSplit != null) { 201 final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit); 202 if (depIdx < 0) { 203 throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx] 204 + "' targets split '" + configForSplit + "', which is missing."); 205 } 206 207 if (!pkg.isFeatureSplits[depIdx]) { 208 throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx] 209 + "' declares itself as configuration split for a non-feature split '" 210 + pkg.splitNames[depIdx] + "'"); 211 } 212 targetSplitIdx = depIdx + 1; 213 } else { 214 targetSplitIdx = 0; 215 } 216 splitDependencies.put(targetSplitIdx, 217 append(splitDependencies.get(targetSplitIdx), splitIdx + 1)); 218 } 219 220 // Verify that there are no cycles. 221 final BitSet bitset = new BitSet(); 222 for (int i = 0, size = splitDependencies.size(); i < size; i++) { 223 int splitIdx = splitDependencies.keyAt(i); 224 225 bitset.clear(); 226 while (splitIdx != -1) { 227 // Check if this split has been visited yet. 228 if (bitset.get(splitIdx)) { 229 throw new IllegalDependencyException("Cycle detected in split dependencies."); 230 } 231 232 // Mark the split so that if we visit it again, we no there is a cycle. 233 bitset.set(splitIdx); 234 235 // Follow the first dependency only, the others are leaves by definition. 236 final int[] deps = splitDependencies.get(splitIdx); 237 splitIdx = deps != null ? deps[0] : -1; 238 } 239 } 240 return splitDependencies; 241 } 242 } 243