blob: edc2379ff6419ef89052ef0168b9718e79890dd3 [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.server.backup;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashSet;
import java.util.Set;
/**
* Records which apps have been backed up on this device, persisting it to disk so that it can be
* read at subsequent boots. This class is threadsafe.
*
* <p>This is used to decide, when restoring a package at install time, whether it has been
* previously backed up on the current device. If it has been previously backed up it should
* restore from the same restore set that the current device has been backing up to. If it has not
* been previously backed up, it should restore from the ancestral restore set (i.e., the restore
* set that the user's previous device was backing up to).
*
* <p>NB: this is always backed by the same files within the state directory supplied at
* construction.
*/
final class ProcessedPackagesJournal {
private static final String TAG = "ProcessedPackagesJournal";
private static final String JOURNAL_FILE_NAME = "processed";
private static final boolean DEBUG = BackupManagerService.DEBUG;
// using HashSet instead of ArraySet since we expect 100-500 elements range
@GuardedBy("mProcessedPackages")
private final Set<String> mProcessedPackages = new HashSet<>();
// TODO: at some point consider splitting the bookkeeping to be per-transport
private final File mStateDirectory;
/**
* Constructs a new journal.
*
* After constructing the object one should call {@link #init()} to load state from disk if
* it has been previously persisted.
*
* @param stateDirectory The directory in which backup state (including journals) is stored.
*/
ProcessedPackagesJournal(File stateDirectory) {
mStateDirectory = stateDirectory;
}
/**
* Loads state from disk if it has been previously persisted.
*/
void init() {
synchronized (mProcessedPackages) {
loadFromDisk();
}
}
/**
* Returns {@code true} if {@code packageName} has previously been backed up.
*/
boolean hasBeenProcessed(String packageName) {
synchronized (mProcessedPackages) {
return mProcessedPackages.contains(packageName);
}
}
void addPackage(String packageName) {
synchronized (mProcessedPackages) {
if (!mProcessedPackages.add(packageName)) {
// This package has already been processed - no need to add it to the journal.
return;
}
File journalFile = new File(mStateDirectory, JOURNAL_FILE_NAME);
try (RandomAccessFile out = new RandomAccessFile(journalFile, "rws")) {
out.seek(out.length());
out.writeUTF(packageName);
} catch (IOException e) {
Slog.e(TAG, "Can't log backup of " + packageName + " to " + journalFile);
}
}
}
/**
* A copy of the current state of the journal.
*
* <p>Used only for dumping out information for logging. {@link #hasBeenProcessed(String)}
* should be used for efficiently checking whether a package has been backed up before by this
* device.
*
* @return The current set of packages that have been backed up previously.
*/
Set<String> getPackagesCopy() {
synchronized (mProcessedPackages) {
return new HashSet<>(mProcessedPackages);
}
}
void reset() {
synchronized (mProcessedPackages) {
mProcessedPackages.clear();
File journalFile = new File(mStateDirectory, JOURNAL_FILE_NAME);
journalFile.delete();
}
}
private void loadFromDisk() {
File journalFile = new File(mStateDirectory, JOURNAL_FILE_NAME);
if (!journalFile.exists()) {
return;
}
try (DataInputStream oldJournal = new DataInputStream(
new BufferedInputStream(new FileInputStream(journalFile)))) {
while (true) {
String packageName = oldJournal.readUTF();
if (DEBUG) {
Slog.v(TAG, " + " + packageName);
}
mProcessedPackages.add(packageName);
}
} catch (EOFException e) {
// Successfully loaded journal file
} catch (IOException e) {
Slog.e(TAG, "Error reading processed packages journal", e);
}
}
}