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 /** 20 * Set up files and directories used in an installation session. Currently only used by Incremental 21 * Installation. For Incremental installation, the expected outcome of this function is: 0) All the 22 * files are in defaultStorage 1) All APK files are in the same directory, bound to mApkStorage, and 23 * bound to the InstallerSession's stage dir. The files are linked from mApkStorage to 24 * defaultStorage. 2) All lib files are in the sub directories as their names suggest, and in the 25 * same parent directory as the APK files. The files are linked from mApkStorage to defaultStorage. 26 * 3) OBB files are in another directory that is different from APK files and lib files, bound to 27 * mObbStorage. The files are linked from mObbStorage to defaultStorage. 28 * 29 * @throws IllegalStateException the session is not an Incremental installation session. 30 */ 31 32 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; 33 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.content.Context; 37 import android.content.pm.DataLoaderParams; 38 import android.content.pm.IDataLoaderStatusListener; 39 import android.content.pm.IPackageLoadingProgressCallback; 40 import android.content.pm.InstallationFileParcel; 41 42 import java.io.File; 43 import java.io.IOException; 44 import java.util.List; 45 import java.util.UUID; 46 47 /** 48 * This class manages storage instances used during a package installation session. 49 * @hide 50 */ 51 public final class IncrementalFileStorages { 52 private static final String TAG = "IncrementalFileStorages"; 53 54 private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; 55 56 private @NonNull final IncrementalManager mIncrementalManager; 57 private @NonNull final File mStageDir; 58 private @Nullable IncrementalStorage mInheritedStorage; 59 private @Nullable IncrementalStorage mDefaultStorage; 60 61 /** 62 * Set up files and directories used in an installation session. Only used by Incremental. 63 * All the files will be created in defaultStorage. 64 * 65 * @throws IllegalStateException the session is not an Incremental installation session. 66 * @throws IOException if fails to setup files or directories. 67 */ initialize(Context context, @NonNull File stageDir, @Nullable File inheritedDir, @NonNull DataLoaderParams dataLoaderParams, @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, @Nullable IStorageHealthListener healthListener, @NonNull List<InstallationFileParcel> addedFiles, @NonNull PerUidReadTimeouts[] perUidReadTimeouts, @Nullable IPackageLoadingProgressCallback progressCallback)68 public static IncrementalFileStorages initialize(Context context, 69 @NonNull File stageDir, 70 @Nullable File inheritedDir, 71 @NonNull DataLoaderParams dataLoaderParams, 72 @Nullable IDataLoaderStatusListener statusListener, 73 @Nullable StorageHealthCheckParams healthCheckParams, 74 @Nullable IStorageHealthListener healthListener, 75 @NonNull List<InstallationFileParcel> addedFiles, 76 @NonNull PerUidReadTimeouts[] perUidReadTimeouts, 77 @Nullable IPackageLoadingProgressCallback progressCallback) throws IOException { 78 IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService( 79 Context.INCREMENTAL_SERVICE); 80 if (incrementalManager == null) { 81 throw new IOException("Failed to obtain incrementalManager."); 82 } 83 84 final IncrementalFileStorages result = new IncrementalFileStorages(stageDir, inheritedDir, 85 incrementalManager, dataLoaderParams); 86 for (InstallationFileParcel file : addedFiles) { 87 if (file.location == LOCATION_DATA_APP) { 88 try { 89 result.addApkFile(file); 90 } catch (IOException e) { 91 throw new IOException( 92 "Failed to add file to IncFS: " + file.name + ", reason: ", e); 93 } 94 } else { 95 throw new IOException("Unknown file location: " + file.location); 96 } 97 } 98 // Register progress loading callback after files have been added 99 if (progressCallback != null) { 100 incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(), 101 progressCallback); 102 } 103 result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener, 104 perUidReadTimeouts); 105 106 return result; 107 } 108 IncrementalFileStorages(@onNull File stageDir, @Nullable File inheritedDir, @NonNull IncrementalManager incrementalManager, @NonNull DataLoaderParams dataLoaderParams)109 private IncrementalFileStorages(@NonNull File stageDir, 110 @Nullable File inheritedDir, 111 @NonNull IncrementalManager incrementalManager, 112 @NonNull DataLoaderParams dataLoaderParams) throws IOException { 113 try { 114 mStageDir = stageDir; 115 mIncrementalManager = incrementalManager; 116 if (inheritedDir != null && IncrementalManager.isIncrementalPath( 117 inheritedDir.getAbsolutePath())) { 118 mInheritedStorage = mIncrementalManager.openStorage( 119 inheritedDir.getAbsolutePath()); 120 if (mInheritedStorage != null) { 121 boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals( 122 dataLoaderParams.getComponentName().getPackageName()); 123 if (systemDataLoader && !mInheritedStorage.isFullyLoaded()) { 124 // System data loader does not support incomplete storages. 125 throw new IOException("Inherited storage has missing pages."); 126 } 127 128 mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(), 129 mInheritedStorage, IncrementalManager.CREATE_MODE_CREATE 130 | IncrementalManager.CREATE_MODE_TEMPORARY_BIND); 131 if (mDefaultStorage == null) { 132 throw new IOException( 133 "Couldn't create linked incremental storage at " + stageDir); 134 } 135 return; 136 } 137 } 138 139 mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(), 140 dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE 141 | IncrementalManager.CREATE_MODE_TEMPORARY_BIND); 142 if (mDefaultStorage == null) { 143 throw new IOException( 144 "Couldn't create incremental storage at " + stageDir); 145 } 146 } catch (IOException e) { 147 cleanUp(); 148 throw e; 149 } 150 } 151 addApkFile(@onNull InstallationFileParcel apk)152 private void addApkFile(@NonNull InstallationFileParcel apk) throws IOException { 153 final String apkName = apk.name; 154 final File targetFile = new File(mStageDir, apkName); 155 if (!targetFile.exists()) { 156 mDefaultStorage.makeFile(apkName, apk.size, null, apk.metadata, apk.signature, null); 157 } 158 } 159 160 /** 161 * Starts or re-starts loading of data. 162 */ startLoading( @onNull DataLoaderParams dataLoaderParams, @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, @Nullable IStorageHealthListener healthListener, @NonNull PerUidReadTimeouts[] perUidReadTimeouts)163 public void startLoading( 164 @NonNull DataLoaderParams dataLoaderParams, 165 @Nullable IDataLoaderStatusListener statusListener, 166 @Nullable StorageHealthCheckParams healthCheckParams, 167 @Nullable IStorageHealthListener healthListener, 168 @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException { 169 if (!mDefaultStorage.startLoading(dataLoaderParams, statusListener, healthCheckParams, 170 healthListener, perUidReadTimeouts)) { 171 throw new IOException( 172 "Failed to start or restart loading data for Incremental installation."); 173 } 174 } 175 176 /** 177 * Creates file in default storage and sets its content. 178 */ makeFile(@onNull String name, @NonNull byte[] content)179 public void makeFile(@NonNull String name, @NonNull byte[] content) throws IOException { 180 mDefaultStorage.makeFile(name, content.length, UUID.randomUUID(), null, null, content); 181 } 182 183 /** 184 * Creates a hardlink from inherited storage to default. 185 */ makeLink(@onNull String relativePath, @NonNull String fromBase, @NonNull String toBase)186 public boolean makeLink(@NonNull String relativePath, @NonNull String fromBase, 187 @NonNull String toBase) throws IOException { 188 if (mInheritedStorage == null) { 189 return false; 190 } 191 final File sourcePath = new File(fromBase, relativePath); 192 final File destPath = new File(toBase, relativePath); 193 mInheritedStorage.makeLink(sourcePath.getAbsolutePath(), mDefaultStorage, 194 destPath.getAbsolutePath()); 195 return true; 196 } 197 198 /** 199 * Permanently disables readlogs. 200 */ disallowReadLogs()201 public void disallowReadLogs() { 202 mDefaultStorage.disallowReadLogs(); 203 } 204 205 /** 206 * Resets the states and unbinds storage instances for an installation session. 207 */ cleanUpAndMarkComplete()208 public void cleanUpAndMarkComplete() { 209 IncrementalStorage defaultStorage = cleanUp(); 210 if (defaultStorage != null) { 211 defaultStorage.onInstallationComplete(); 212 } 213 } 214 cleanUp()215 private IncrementalStorage cleanUp() { 216 IncrementalStorage defaultStorage = mDefaultStorage; 217 mInheritedStorage = null; 218 mDefaultStorage = null; 219 if (defaultStorage == null) { 220 return null; 221 } 222 223 try { 224 mIncrementalManager.unregisterLoadingProgressCallbacks(mStageDir.getAbsolutePath()); 225 defaultStorage.unBind(mStageDir.getAbsolutePath()); 226 } catch (IOException ignored) { 227 } 228 return defaultStorage; 229 } 230 } 231