blob: 187d5d93bfddf2df0e8c31fb6f23c992c6598c4d [file] [log] [blame]
Robert Berrya1109ef2017-07-17 09:56:44 +01001/*
2 * Copyright (C) 2017 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
17package com.android.server.backup;
18
19import android.util.Slog;
20
21import com.android.internal.annotations.GuardedBy;
22import com.android.server.backup.RefactoredBackupManagerService;
23
24import java.io.EOFException;
25import java.io.File;
26import java.io.IOException;
27import java.io.RandomAccessFile;
28import java.util.HashSet;
29import java.util.Set;
30
31/**
32 * Records which apps have been backed up on this device, persisting it to disk so that it can be
33 * read at subsequent boots. This class is threadsafe.
34 *
35 * <p>This is used to decide, when restoring a package at install time, whether it has been
36 * previously backed up on the current device. If it has been previously backed up it should
37 * restore from the same restore set that the current device has been backing up to. If it has not
38 * been previously backed up, it should restore from the ancestral restore set (i.e., the restore
39 * set that the user's previous device was backing up to).
40 *
41 * <p>NB: this is always backed by the same files within the state directory supplied at
42 * construction.
43 */
44final class ProcessedPackagesJournal {
45 private static final String TAG = "ProcessedPackagesJournal";
46 private static final String JOURNAL_FILE_NAME = "processed";
47 private static final boolean DEBUG = RefactoredBackupManagerService.DEBUG || false;
48
49 // using HashSet instead of ArraySet since we expect 100-500 elements range
50 @GuardedBy("mProcessedPackages")
51 private final Set<String> mProcessedPackages = new HashSet<>();
52 // TODO: at some point consider splitting the bookkeeping to be per-transport
53 private final File mStateDirectory;
54
55 /**
56 * Constructs a new journal.
57 *
58 * After constructing the object one should call {@link #init()} to load state from disk if
59 * it has been previously persisted.
60 *
61 * @param stateDirectory The directory in which backup state (including journals) is stored.
62 */
63 ProcessedPackagesJournal(File stateDirectory) {
64 mStateDirectory = stateDirectory;
65 }
66
67 /**
68 * Loads state from disk if it has been previously persisted.
69 */
70 void init() {
71 synchronized (mProcessedPackages) {
72 loadFromDisk();
73 }
74 }
75
76 /**
77 * Returns {@code true} if {@code packageName} has previously been backed up.
78 */
79 boolean hasBeenProcessed(String packageName) {
80 synchronized (mProcessedPackages) {
81 return mProcessedPackages.contains(packageName);
82 }
83 }
84
85 void addPackage(String packageName) {
86 synchronized (mProcessedPackages) {
87 if (!mProcessedPackages.add(packageName)) {
88 // This package has already been processed - no need to add it to the journal.
89 return;
90 }
91
92 File journalFile = new File(mStateDirectory, JOURNAL_FILE_NAME);
93
94 try (RandomAccessFile out = new RandomAccessFile(journalFile, "rws")) {
95 out.seek(out.length());
96 out.writeUTF(packageName);
97 } catch (IOException e) {
98 Slog.e(TAG, "Can't log backup of " + packageName + " to " + journalFile);
99 }
100 }
101 }
102
103 /**
104 * A copy of the current state of the journal.
105 *
106 * <p>Used only for dumping out information for logging. {@link #hasBeenProcessed(String)}
107 * should be used for efficiently checking whether a package has been backed up before by this
108 * device.
109 *
110 * @return The current set of packages that have been backed up previously.
111 */
112 Set<String> getPackagesCopy() {
113 synchronized (mProcessedPackages) {
114 return new HashSet<>(mProcessedPackages);
115 }
116 }
117
118 void reset() {
119 synchronized (mProcessedPackages) {
120 mProcessedPackages.clear();
121 File journalFile = new File(mStateDirectory, JOURNAL_FILE_NAME);
122 journalFile.delete();
123 }
124 }
125
126 private void loadFromDisk() {
127 File journalFile = new File(mStateDirectory, JOURNAL_FILE_NAME);
128
129 if (!journalFile.exists()) {
130 return;
131 }
132
133 try (RandomAccessFile oldJournal = new RandomAccessFile(journalFile, "r")) {
134 while (true) {
135 String packageName = oldJournal.readUTF();
136 if (DEBUG) {
137 Slog.v(TAG, " + " + packageName);
138 }
139 mProcessedPackages.add(packageName);
140 }
141 } catch (EOFException e) {
142 // Successfully loaded journal file
143 } catch (IOException e) {
144 Slog.e(TAG, "Error reading processed packages journal", e);
145 }
146 }
147}