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 android.os.incremental; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SystemService; 23 import android.content.Context; 24 import android.content.pm.DataLoaderParams; 25 import android.content.pm.IDataLoaderStatusListener; 26 import android.os.RemoteException; 27 import android.util.SparseArray; 28 29 import com.android.internal.annotations.GuardedBy; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.nio.file.FileVisitResult; 36 import java.nio.file.Files; 37 import java.nio.file.Path; 38 import java.nio.file.Paths; 39 import java.nio.file.SimpleFileVisitor; 40 import java.nio.file.attribute.BasicFileAttributes; 41 42 /** 43 * Provides operations to open or create an IncrementalStorage, using IIncrementalService 44 * service. Example Usage: 45 * 46 * <blockquote><pre> 47 * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE); 48 * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir"); 49 * </pre></blockquote> 50 * 51 * @hide 52 */ 53 @SystemService(Context.INCREMENTAL_SERVICE) 54 public final class IncrementalManager { 55 private static final String TAG = "IncrementalManager"; 56 57 private static final String ALLOWED_PROPERTY = "incremental.allowed"; 58 59 public static final int CREATE_MODE_TEMPORARY_BIND = 60 IIncrementalService.CREATE_MODE_TEMPORARY_BIND; 61 public static final int CREATE_MODE_PERMANENT_BIND = 62 IIncrementalService.CREATE_MODE_PERMANENT_BIND; 63 public static final int CREATE_MODE_CREATE = 64 IIncrementalService.CREATE_MODE_CREATE; 65 public static final int CREATE_MODE_OPEN_EXISTING = 66 IIncrementalService.CREATE_MODE_OPEN_EXISTING; 67 68 @Retention(RetentionPolicy.SOURCE) 69 @IntDef(prefix = {"CREATE_MODE_"}, value = { 70 CREATE_MODE_TEMPORARY_BIND, 71 CREATE_MODE_PERMANENT_BIND, 72 CREATE_MODE_CREATE, 73 CREATE_MODE_OPEN_EXISTING, 74 }) 75 public @interface CreateMode { 76 } 77 78 private final @Nullable IIncrementalService mService; 79 @GuardedBy("mStorages") 80 private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>(); 81 IncrementalManager(IIncrementalService service)82 public IncrementalManager(IIncrementalService service) { 83 mService = service; 84 } 85 86 /** 87 * Returns a storage object given a storage ID. 88 * 89 * @param storageId The storage ID to identify the storage object. 90 * @return IncrementalStorage object corresponding to storage ID. 91 */ 92 // TODO(b/136132412): remove this 93 @Nullable getStorage(int storageId)94 public IncrementalStorage getStorage(int storageId) { 95 synchronized (mStorages) { 96 return mStorages.get(storageId); 97 } 98 } 99 100 /** 101 * Opens or create an Incremental File System mounted directory and returns an 102 * IncrementalStorage object. 103 * 104 * @param path Absolute path to mount Incremental File System on. 105 * @param params IncrementalDataLoaderParams object to configure data loading. 106 * @param createMode Mode for opening an old Incremental File System mount or creating 107 * a new mount. 108 * @param autoStartDataLoader Set true to immediately start data loader after creating storage. 109 * @return IncrementalStorage object corresponding to the mounted directory. 110 */ 111 @Nullable createStorage(@onNull String path, @NonNull DataLoaderParams params, @CreateMode int createMode, boolean autoStartDataLoader, @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, @Nullable IStorageHealthListener healthListener)112 public IncrementalStorage createStorage(@NonNull String path, 113 @NonNull DataLoaderParams params, 114 @CreateMode int createMode, 115 boolean autoStartDataLoader, 116 @Nullable IDataLoaderStatusListener statusListener, 117 @Nullable StorageHealthCheckParams healthCheckParams, 118 @Nullable IStorageHealthListener healthListener) { 119 try { 120 final int id = mService.createStorage(path, params.getData(), createMode, 121 statusListener, healthCheckParams, healthListener); 122 if (id < 0) { 123 return null; 124 } 125 final IncrementalStorage storage = new IncrementalStorage(mService, id); 126 synchronized (mStorages) { 127 mStorages.put(id, storage); 128 } 129 if (autoStartDataLoader) { 130 storage.startLoading(); 131 } 132 return storage; 133 } catch (RemoteException e) { 134 throw e.rethrowFromSystemServer(); 135 } 136 } 137 138 /** 139 * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage 140 * object. 141 * 142 * @param path Absolute target path that Incremental File System has been mounted on. 143 * @return IncrementalStorage object corresponding to the mounted directory. 144 */ 145 @Nullable openStorage(@onNull String path)146 public IncrementalStorage openStorage(@NonNull String path) { 147 try { 148 final int id = mService.openStorage(path); 149 if (id < 0) { 150 return null; 151 } 152 final IncrementalStorage storage = new IncrementalStorage(mService, id); 153 synchronized (mStorages) { 154 mStorages.put(id, storage); 155 } 156 return storage; 157 } catch (RemoteException e) { 158 throw e.rethrowFromSystemServer(); 159 } 160 } 161 162 /** 163 * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage. 164 * 165 * @return IncrementalStorage object corresponding to the linked storage. 166 */ 167 @Nullable createStorage(@onNull String path, @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode)168 public IncrementalStorage createStorage(@NonNull String path, 169 @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) { 170 try { 171 final int id = mService.createLinkedStorage( 172 path, linkedStorage.getId(), createMode); 173 if (id < 0) { 174 return null; 175 } 176 final IncrementalStorage storage = new IncrementalStorage(mService, id); 177 synchronized (mStorages) { 178 mStorages.put(id, storage); 179 } 180 return storage; 181 } catch (RemoteException e) { 182 throw e.rethrowFromSystemServer(); 183 } 184 } 185 186 /** 187 * Set up an app's code path. The expected outcome of this method is: 188 * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory 189 * of {@code afterCodeFile}. 190 * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}. 191 * 192 * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it. 193 * Should no longer have any APKs after this method is called. 194 * Example: /data/app/vmdl*tmp 195 * @param afterCodeFile Path that should will have APKs after this method is called. Its parent 196 * directory should be bind-mounted to a directory under /data/incremental. 197 * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB] 198 * @throws IllegalArgumentException 199 * @throws IOException 200 * TODO(b/147371381): add unit tests 201 */ renameCodePath(File beforeCodeFile, File afterCodeFile)202 public void renameCodePath(File beforeCodeFile, File afterCodeFile) 203 throws IllegalArgumentException, IOException { 204 final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile(); 205 final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString()); 206 if (apkStorage == null) { 207 throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute); 208 } 209 final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent(); 210 final IncrementalStorage linkedApkStorage = 211 createStorage(targetStorageDir, apkStorage, 212 IncrementalManager.CREATE_MODE_CREATE 213 | IncrementalManager.CREATE_MODE_PERMANENT_BIND); 214 if (linkedApkStorage == null) { 215 throw new IOException("Failed to create linked storage at dir: " + targetStorageDir); 216 } 217 try { 218 final String afterCodePathName = afterCodeFile.getName(); 219 linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName); 220 apkStorage.unBind(beforeCodeAbsolute.toString()); 221 } catch (Exception e) { 222 linkedApkStorage.unBind(targetStorageDir); 223 throw e; 224 } 225 } 226 227 /** 228 * Recursively set up directories and link all the files from source storage to target storage. 229 * 230 * @param sourceStorage The storage that has all the files and directories underneath. 231 * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs. 232 * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib". 233 * @param targetStorage The target storage that will have the same files and directories. 234 * @param targetRelativePath The relative path to the directory on the target storage that 235 * should have all the files and dirs underneath, 236 * e.g., "packageName-random". 237 * @throws IOException When makeDirectory or makeLink fails on the Incremental File System. 238 */ linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, String sourceRelativePath, IncrementalStorage targetStorage, String targetRelativePath)239 private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, 240 String sourceRelativePath, IncrementalStorage targetStorage, 241 String targetRelativePath) throws IOException { 242 final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath); 243 final Path targetRelative = Paths.get(targetRelativePath); 244 Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() { 245 @Override 246 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 247 throws IOException { 248 final Path relativeDir = sourceBase.relativize(dir); 249 targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString()); 250 return FileVisitResult.CONTINUE; 251 } 252 253 @Override 254 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 255 throws IOException { 256 final Path relativeFile = sourceBase.relativize(file); 257 sourceStorage.makeLink( 258 file.toAbsolutePath().toString(), targetStorage, 259 targetRelative.resolve(relativeFile).toString()); 260 return FileVisitResult.CONTINUE; 261 } 262 }); 263 } 264 265 /** 266 * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing. 267 * Unbinds the target dir and deletes the corresponding storage instance. 268 */ closeStorage(@onNull String path)269 public void closeStorage(@NonNull String path) { 270 try { 271 final int id = mService.openStorage(path); 272 if (id < 0) { 273 return; 274 } 275 mService.deleteStorage(id); 276 synchronized (mStorages) { 277 mStorages.remove(id); 278 } 279 } catch (RemoteException e) { 280 throw e.rethrowFromSystemServer(); 281 } 282 } 283 284 /** 285 * Checks if Incremental feature is enabled on this device. 286 */ isFeatureEnabled()287 public static boolean isFeatureEnabled() { 288 return nativeIsEnabled(); 289 } 290 291 /** 292 * Checks if Incremental installations are allowed. 293 * A developer can disable Incremental installations by setting the property. 294 */ isAllowed()295 public static boolean isAllowed() { 296 return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true); 297 } 298 299 /** 300 * Checks if path is mounted on Incremental File System. 301 */ isIncrementalPath(@onNull String path)302 public static boolean isIncrementalPath(@NonNull String path) { 303 return nativeIsIncrementalPath(path); 304 } 305 306 /** 307 * Returns raw signature for file if it's on Incremental File System. 308 * Unsafe, use only if you are sure what you are doing. 309 */ unsafeGetFileSignature(@onNull String path)310 public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) { 311 return nativeUnsafeGetFileSignature(path); 312 } 313 314 /* Native methods */ nativeIsEnabled()315 private static native boolean nativeIsEnabled(); nativeIsIncrementalPath(@onNull String path)316 private static native boolean nativeIsIncrementalPath(@NonNull String path); nativeUnsafeGetFileSignature(@onNull String path)317 private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path); 318 } 319