| /* |
| * 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); |
| } |
| } |
| } |