blob: 321dc9e2246e17885afe6afc9d4376ef4aa1814e [file] [log] [blame]
/*
* Copyright (C) 2019 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.os.incremental;
/**
* Set up files and directories used in an installation session. Currently only used by Incremental
* Installation. For Incremental installation, the expected outcome of this function is: 0) All the
* files are in defaultStorage 1) All APK files are in the same directory, bound to mApkStorage, and
* bound to the InstallerSession's stage dir. The files are linked from mApkStorage to
* defaultStorage. 2) All lib files are in the sub directories as their names suggest, and in the
* same parent directory as the APK files. The files are linked from mApkStorage to defaultStorage.
* 3) OBB files are in another directory that is different from APK files and lib files, bound to
* mObbStorage. The files are linked from mObbStorage to defaultStorage.
*
* @throws IllegalStateException the session is not an Incremental installation session.
*/
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.InstallationFileParcel;
import android.text.TextUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* This class manages storage instances used during a package installation session.
* @hide
*/
public final class IncrementalFileStorages {
private static final String TAG = "IncrementalFileStorages";
private @NonNull final IncrementalManager mIncrementalManager;
private @NonNull final File mStageDir;
private @Nullable IncrementalStorage mDefaultStorage;
/**
* Set up files and directories used in an installation session. Only used by Incremental.
* All the files will be created in defaultStorage.
* TODO(b/133435829): code clean up
*
* @throws IllegalStateException the session is not an Incremental installation session.
* @throws IOException if fails to setup files or directories.
*/
public static IncrementalFileStorages initialize(Context context,
@NonNull File stageDir,
@NonNull DataLoaderParams dataLoaderParams,
@Nullable IDataLoaderStatusListener dataLoaderStatusListener,
List<InstallationFileParcel> addedFiles) throws IOException {
// TODO(b/136132412): sanity check if session should not be incremental
IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
Context.INCREMENTAL_SERVICE);
if (incrementalManager == null) {
// TODO(b/146080380): add incremental-specific error code
throw new IOException("Failed to obtain incrementalManager.");
}
final IncrementalFileStorages result =
new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams,
dataLoaderStatusListener);
for (InstallationFileParcel file : addedFiles) {
if (file.location == LOCATION_DATA_APP) {
try {
result.addApkFile(file);
} catch (IOException e) {
// TODO(b/146080380): add incremental-specific error code
throw new IOException(
"Failed to add file to IncFS: " + file.name + ", reason: ", e);
}
} else {
throw new IOException("Unknown file location: " + file.location);
}
}
if (!result.mDefaultStorage.startLoading()) {
// TODO(b/146080380): add incremental-specific error code
throw new IOException("Failed to start loading data for Incremental installation.");
}
return result;
}
private IncrementalFileStorages(@NonNull File stageDir,
@NonNull IncrementalManager incrementalManager,
@NonNull DataLoaderParams dataLoaderParams,
@Nullable IDataLoaderStatusListener dataLoaderStatusListener) throws IOException {
try {
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
final String incrementalPath = dataLoaderParams.getArguments();
if (TextUtils.isEmpty(incrementalPath)) {
throw new IOException("Failed to create storage: incrementalPath is empty");
}
mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
if (mDefaultStorage == null) {
throw new IOException(
"Couldn't open incremental storage at " + incrementalPath);
}
mDefaultStorage.bind(stageDir.getAbsolutePath());
} else {
mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
dataLoaderParams,
dataLoaderStatusListener,
IncrementalManager.CREATE_MODE_CREATE
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
if (mDefaultStorage == null) {
throw new IOException(
"Couldn't create incremental storage at " + stageDir);
}
}
} catch (IOException e) {
cleanUp();
throw e;
}
}
private void addApkFile(@NonNull InstallationFileParcel apk) throws IOException {
final String apkName = apk.name;
final File targetFile = new File(mStageDir, apkName);
if (!targetFile.exists()) {
mDefaultStorage.makeFile(apkName, apk.size, null, apk.metadata, apk.signature);
}
}
/**
* Resets the states and unbinds storage instances for an installation session.
* TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
*/
public void cleanUp() {
if (mDefaultStorage == null) {
return;
}
try {
mDefaultStorage.unBind(mStageDir.getAbsolutePath());
} catch (IOException ignored) {
}
mDefaultStorage = null;
}
}