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